diff options
387 files changed, 11644 insertions, 2726 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index c43aa7518f32..f5bf437a738d 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -13,71 +13,143 @@ // limitations under the License. aconfig_srcjars = [ - ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! + ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", + ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}", + ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.app.smartspace.flags-aconfig-java{.generated_srcjars}", + ":android.app.usage.flags-aconfig-java{.generated_srcjars}", + ":android.appwidget.flags-aconfig-java{.generated_srcjars}", + ":android.chre.flags-aconfig-java{.generated_srcjars}", ":android.companion.flags-aconfig-java{.generated_srcjars}", + ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", + ":android.content.flags-aconfig-java{.generated_srcjars}", ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.content.res.flags-aconfig-java{.generated_srcjars}", + ":android.credentials.flags-aconfig-java{.generated_srcjars}", + ":android.database.sqlite-aconfig-java{.generated_srcjars}", + ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":android.hardware.flags-aconfig-java{.generated_srcjars}", ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}", + ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", ":android.location.flags-aconfig-java{.generated_srcjars}", + ":android.media.tv.flags-aconfig-java{.generated_srcjars}", + ":android.multiuser.flags-aconfig-java{.generated_srcjars}", ":android.net.vcn.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", + ":android.permission.flags-aconfig-java{.generated_srcjars}", + ":android.provider.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", ":android.server.app.flags-aconfig-java{.generated_srcjars}", + ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", + ":android.service.controls.flags-aconfig-java{.generated_srcjars}", ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", - ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.service.voice.flags-aconfig-java{.generated_srcjars}", + ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":android.tracing.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", + ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", + ":android.view.flags-aconfig-java{.generated_srcjars}", + ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", + ":android.webkit.flags-aconfig-java{.generated_srcjars}", + ":android.widget.flags-aconfig-java{.generated_srcjars}", ":audio-framework-aconfig", ":camera_platform_flags_core_java_lib{.generated_srcjars}", - ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", - ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":com.android.hardware.input-aconfig-java{.generated_srcjars}", ":com.android.input.flags-aconfig-java{.generated_srcjars}", - ":com.android.text.flags-aconfig-java{.generated_srcjars}", - ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", - ":telecom_flags_core_java_lib{.generated_srcjars}", - ":telephony_flags_core_java_lib{.generated_srcjars}", - ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}", - ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}", - ":android.widget.flags-aconfig-java{.generated_srcjars}", - ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", - ":sdk_sandbox_flags_lib{.generated_srcjars}", - ":android.permission.flags-aconfig-java{.generated_srcjars}", - ":android.database.sqlite-aconfig-java{.generated_srcjars}", - ":hwui_flags_java_lib{.generated_srcjars}", - ":framework_graphics_flags_java_lib{.generated_srcjars}", - ":display_flags_lib{.generated_srcjars}", ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}", - ":android.multiuser.flags-aconfig-java{.generated_srcjars}", - ":android.app.flags-aconfig-java{.generated_srcjars}", - ":android.credentials.flags-aconfig-java{.generated_srcjars}", - ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", - ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", - ":android.service.controls.flags-aconfig-java{.generated_srcjars}", - ":android.service.voice.flags-aconfig-java{.generated_srcjars}", - ":android.media.tv.flags-aconfig-java{.generated_srcjars}", - ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":com.android.net.flags-aconfig-java{.generated_srcjars}", + ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", + ":com.android.text.flags-aconfig-java{.generated_srcjars}", + ":com.android.window.flags.window-aconfig-java{.generated_srcjars}", ":device_policy_aconfig_flags_lib{.generated_srcjars}", - ":surfaceflinger_flags_java_lib{.generated_srcjars}", - ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}", - ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}", - ":android.tracing.flags-aconfig-java{.generated_srcjars}", - ":android.appwidget.flags-aconfig-java{.generated_srcjars}", - ":android.webkit.flags-aconfig-java{.generated_srcjars}", - ":android.provider.flags-aconfig-java{.generated_srcjars}", - ":android.chre.flags-aconfig-java{.generated_srcjars}", - ":android.speech.flags-aconfig-java{.generated_srcjars}", + ":display_flags_lib{.generated_srcjars}", + ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}", + ":framework_graphics_flags_java_lib{.generated_srcjars}", + ":hwui_flags_java_lib{.generated_srcjars}", ":power_flags_lib{.generated_srcjars}", - ":android.content.flags-aconfig-java{.generated_srcjars}", - ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}", + ":sdk_sandbox_flags_lib{.generated_srcjars}", + ":surfaceflinger_flags_java_lib{.generated_srcjars}", + ":telecom_flags_core_java_lib{.generated_srcjars}", + ":telephony_flags_core_java_lib{.generated_srcjars}", + // !!! KEEP THIS LIST ALPHABETICAL !!! ] +stubs_defaults { + name: "framework-minus-apex-aconfig-declarations", + aconfig_declarations: [ + "android.app.flags-aconfig", + "android.app.smartspace.flags-aconfig", + "android.app.usage.flags-aconfig", + "android.appwidget.flags-aconfig", + "android.companion.flags-aconfig", + "android.companion.virtual.flags-aconfig", + "android.content.pm.flags-aconfig", + "android.content.res.flags-aconfig", + "android.credentials.flags-aconfig", + "android.database.sqlite-aconfig", + "android.hardware.biometrics.flags-aconfig", + "android.hardware.flags-aconfig", + "android.hardware.radio.flags-aconfig", + "android.hardware.usb.flags-aconfig", + "android.location.flags-aconfig", + "android.media.audio-aconfig", + "android.media.audiopolicy-aconfig", + "android.media.midi-aconfig", + "android.media.tv.flags-aconfig", + "android.multiuser.flags-aconfig", + "android.net.vcn.flags-aconfig", + "android.nfc.flags-aconfig", + "android.os.flags-aconfig", + "android.os.vibrator.flags-aconfig", + "android.permission.flags-aconfig", + "android.provider.flags-aconfig", + "android.security.flags-aconfig", + "android.server.app.flags-aconfig", + "android.service.autofill.flags-aconfig", + "android.service.chooser.flags-aconfig", + "android.service.controls.flags-aconfig", + "android.service.dreams.flags-aconfig", + "android.service.notification.flags-aconfig", + "android.service.voice.flags-aconfig", + "android.speech.flags-aconfig", + "android.tracing.flags-aconfig", + "android.view.accessibility.flags-aconfig", + "android.view.contentcapture.flags-aconfig", + "android.view.contentprotection.flags-aconfig", + "android.view.flags-aconfig", + "android.view.inputmethod.flags-aconfig", + "android.webkit.flags-aconfig", + "android.widget.flags-aconfig", + "camera_platform_flags", + "chre_flags", + "com.android.hardware.input.input-aconfig", + "com.android.input.flags-aconfig", + "com.android.media.flags.bettertogether-aconfig", + "com.android.net.flags-aconfig", + "com.android.server.flags.services-aconfig", + "com.android.text.flags-aconfig", + "com.android.window.flags.window-aconfig", + "device_policy_aconfig_flags", + "display_flags", + "fold_lock_setting_flags", + "framework-jobscheduler-job.flags-aconfig", + "framework_graphics_flags", + "hwui_flags", + "power_flags", + "sdk_sandbox_flags", + "surfaceflinger_flags", + "telecom_flags", + "telephony_flags", + ], +} + filegroup { name: "framework-minus-apex-aconfig-srcjars", srcs: aconfig_srcjars, @@ -962,3 +1034,16 @@ java_aconfig_library { aconfig_declarations: "android.content.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Adaptive Auth +aconfig_declarations { + name: "android.adaptiveauth.flags-aconfig", + package: "android.adaptiveauth", + srcs: ["core/java/android/adaptiveauth/*.aconfig"], +} + +java_aconfig_library { + name: "android.adaptiveauth.flags-aconfig-java", + aconfig_declarations: "android.adaptiveauth.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 485f1be42017..9c56733650cb 100644 --- a/Android.bp +++ b/Android.bp @@ -216,7 +216,6 @@ java_library { "apex_aidl_interface-java", "packagemanager_aidl-java", "framework-protos", - "libtombstone_proto_java", "updatable-driver-protos", "ota_metadata_proto_java", "android.hidl.base-V1.0-java", diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index d03bbd249b00..e7adf203334e 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -34,6 +34,7 @@ gensrcs { ":ipconnectivity-proto-src", ":libstats_atom_enum_protos", ":libstats_atom_message_protos", + ":libtombstone_proto-src", "core/proto/**/*.proto", "libs/incident/**/*.proto", ], diff --git a/Ravenwood.bp b/Ravenwood.bp index d13c4d78190c..0877bcedb609 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -97,6 +97,7 @@ android_ravenwood_libgroup { "framework-minus-apex.ravenwood", "hoststubgen-helper-runtime.ravenwood", "hoststubgen-helper-framework-runtime.ravenwood", + "core-libart-for-host", "all-updatable-modules-system-stubs", "junit", "truth", diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS deleted file mode 100644 index a7ecb4dfdd44..000000000000 --- a/STABILITY_OWNERS +++ /dev/null @@ -1,2 +0,0 @@ -gaillard@google.com - diff --git a/TEST_MAPPING b/TEST_MAPPING index d59775f4060b..c904eb46d88e 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -138,15 +138,13 @@ } ], "postsubmit-ravenwood": [ - // TODO(ravenwood) promote it to presubmit - // TODO: Enable it once the infra knows how to run it. -// { -// "name": "CtsUtilTestCasesRavenwood", -// "file_patterns": [ -// "*Ravenwood*", -// "*ravenwood*" -// ] -// } + { + "name": "CtsUtilTestCasesRavenwood", + "host": true, + "file_patterns": [ + "[Rr]avenwood" + ] + } ], "postsubmit-managedprofile-stress": [ { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 2c9af67ecdc4..44afbe6aff51 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -951,7 +951,7 @@ public final class FlexibilityController extends StateController { @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting - static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS; + static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS; private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS; @VisibleForTesting final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80}; diff --git a/api/Android.bp b/api/Android.bp index 1686943d08ca..b3b18b66e097 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -60,8 +60,40 @@ metalava_cmd = "$(location metalava)" metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED " metalava_cmd += " --quiet " +soong_config_module_type { + name: "enable_crashrecovery_module", + module_type: "combined_apis_defaults", + config_namespace: "ANDROID", + bool_variables: ["release_crashrecovery_module"], + properties: [ + "bootclasspath", + "system_server_classpath", + ], +} + +soong_config_bool_variable { + name: "release_crashrecovery_module", +} + +enable_crashrecovery_module { + name: "crashrecovery_module_defaults", + soong_config_variables: { + release_crashrecovery_module: { + bootclasspath: [ + "framework-crashrecovery", + ], + system_server_classpath: [ + "service-crashrecovery", + ], + }, + }, +} + combined_apis { name: "frameworks-base-api", + defaults: [ + "crashrecovery_module_defaults", + ], bootclasspath: [ "android.net.ipsec.ike", "art.module.public.api", @@ -269,6 +301,7 @@ packages_to_document = [ // classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", + defaults: ["framework-minus-apex-aconfig-declarations"], srcs: [":android-non-updatable-stub-sources"], sdk_version: "none", system_modules: "none", diff --git a/boot/Android.bp b/boot/Android.bp index c4a1139b70a4..228d060bf9cf 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -30,6 +30,7 @@ soong_config_module_type { bool_variables: [ "car_bootclasspath_fragment", "nfc_apex_bootclasspath_fragment", + "release_crashrecovery_module", ], properties: [ "fragments", @@ -165,6 +166,15 @@ custom_platform_bootclasspath { }, ], }, + release_crashrecovery_module: { + fragments: [ + // only used when crashrecovery is enabled + { + apex: "com.android.crashrecovery", + module: "com.android.crashrecovery-bootclasspath-fragment", + }, + ], + }, }, // Additional information needed by hidden api processing. diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp index 16bb896e939c..55ea15d0cdf1 100644 --- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp +++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp @@ -468,9 +468,9 @@ Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info entry.name().c_str()); const auto& res_value = entry.res_value(); result.pairs.emplace_back(OverlayData::Value{ - name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{ + name, TargetValueWithConfig{.value = TargetValue{ .data_type = static_cast<uint8_t>(res_value.data_type()), - .data_value = res_value.data_value()}}}); + .data_value = res_value.data_value()}, .config = entry.configuration()}}); } } } diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp index 7869fbdb8cea..3c0e118bbfe7 100644 --- a/cmds/idmap2/libidmap2/ResourceContainer.cpp +++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp @@ -227,9 +227,9 @@ Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider } else { overlay_data.pairs.emplace_back( OverlayData::Value{*target_resource, TargetValueWithConfig{ - .config = std::string(), .value = TargetValue{.data_type = overlay_resource->dataType, - .data_value = overlay_resource->data}}}); + .data_value = overlay_resource->data}, + .config = std::string()}}); } } @@ -268,10 +268,11 @@ struct ResState { std::unique_ptr<AssetManager2> am; ZipAssetsProvider* zip_assets; - static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) { + static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip, + package_property_t flags) { ResState state; state.zip_assets = zip.get(); - if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) { + if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) { return Error("failed to load apk asset"); } @@ -284,7 +285,7 @@ struct ResState { } state.am = std::make_unique<AssetManager2>(); - if (!state.am->SetApkAssets({state.apk_assets})) { + if (!state.am->SetApkAssets({state.apk_assets}, false)) { return Error("failed to create asset manager"); } @@ -343,8 +344,8 @@ Result<const ResState*> ApkResourceContainer::GetState() const { return state; } - auto state = - ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_))); + auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)), + PROPERTY_OPTIMIZE_NAME_LOOKUPS); if (!state) { return state.GetError(); } diff --git a/core/api/current.txt b/core/api/current.txt index ce7c0440fd15..f55de5ae2d26 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -12445,7 +12445,7 @@ package android.content.pm { method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback); method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler); method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException; - method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException; method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender); method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender); @@ -12872,7 +12872,6 @@ package android.content.pm { field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3 field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1 field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10 - field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20 field public static final int DONT_KILL_APP = 1; // 0x1 field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID"; field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT"; @@ -13675,11 +13674,8 @@ package android.content.res { @FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter { method public float convertDpToSp(float); method public float convertSpToDp(float); - } - - @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory { - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); - method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float); + method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float); + method @AnyThread public static boolean isNonLinearFontScalingActive(float); } public class ObbInfo implements android.os.Parcelable { @@ -16399,6 +16395,8 @@ package android.graphics { field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0 field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10 field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80 + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000 + field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000 field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8 } @@ -18682,6 +18680,8 @@ package android.hardware.biometrics { method @Nullable public int getAllowedAuthenticators(); method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView(); method @Nullable public CharSequence getDescription(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap(); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes(); method @Nullable public CharSequence getNegativeButtonText(); method @Nullable public CharSequence getSubtitle(); method @NonNull public CharSequence getTitle(); @@ -18731,6 +18731,8 @@ package android.hardware.biometrics { method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence); method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int); + method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence); method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence); @@ -24304,7 +24306,7 @@ package android.media { method public void release(); method public void selectRoute(@NonNull android.media.MediaRoute2Info); method public void setVolume(int); - method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf(); + method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf(); } public abstract static class MediaRouter2.TransferCallback { @@ -31850,6 +31852,7 @@ package android.os { method public long computeChargeTimeRemaining(); method public int getIntProperty(int); method public long getLongProperty(int); + method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int); method public boolean isCharging(); field public static final String ACTION_CHARGING = "android.os.action.CHARGING"; field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING"; @@ -43077,6 +43080,8 @@ package android.telephony { field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 529671251087..c394ab7d9f7e 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -133,6 +133,7 @@ package android { field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; + field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"; field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE"; field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS"; field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; @@ -294,6 +295,7 @@ package android { field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS"; field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; + field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"; field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL"; @@ -1615,6 +1617,7 @@ package android.app.ambientcontext { field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4 field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2 field public static final int LEVEL_UNKNOWN = 0; // 0x0 + field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff } public static final class AmbientContextEvent.Builder { @@ -9983,6 +9986,7 @@ package android.nfc.cardemulation { @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable { ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); @@ -9993,6 +9997,7 @@ package android.nfc.cardemulation { method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement(); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids(); @@ -10005,6 +10010,7 @@ package android.nfc.cardemulation { method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement(); @@ -10043,12 +10049,17 @@ package android.os { field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7 + field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc + field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3 field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2 field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4 field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1 field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS"; field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP"; + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1 + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2 + field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0 } public final class BatterySaverPolicyConfig implements android.os.Parcelable { @@ -17164,6 +17175,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0d1d8d733139..a5c2af76479b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -483,10 +483,15 @@ package android.app { } public class UiModeManager { + method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay(); method public boolean isNightModeLocked(); method public boolean isUiModeLocked(); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int); method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int); + field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea + field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9 + field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8 + field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1 field public static final int PROJECTION_TYPE_NONE = 0; // 0x0 diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS new file mode 100644 index 000000000000..0218a7835586 --- /dev/null +++ b/core/java/android/adaptiveauth/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig new file mode 100644 index 000000000000..39e46bbdfa6a --- /dev/null +++ b/core/java/android/adaptiveauth/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.adaptiveauth" + +flag { + name: "report_biometric_auth_attempts" + namespace: "biometrics" + description: "Control the usage of the biometric auth signal in adaptive auth" + bug: "285053096" +}
\ No newline at end of file diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 669baf9549cc..4b2e93fda171 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1540,9 +1540,16 @@ public class AppOpsManager { public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER; + /** + * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}. + * @hide + */ + public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER = + AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 143; + public static final int _NUM_OP = 144; /** * All app ops represented as strings. @@ -2375,6 +2382,14 @@ public class AppOpsManager { public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER = "android:rapid_clear_notifications_by_listener"; + /** + * Allows an application to read the system grammatical gender. + * + * @hide + */ + public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER = + "android:read_system_grammatical_gender"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2486,6 +2501,7 @@ public class AppOpsManager { OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, + OP_READ_SYSTEM_GRAMMATICAL_GENDER, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2938,6 +2954,10 @@ public class AppOpsManager { OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER, "RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER") .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER, + OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER") + .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER) + .build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index cc3eac1bbf01..afa513dbaaef 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -26,11 +26,19 @@ import android.icu.text.SimpleDateFormat; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.text.TextUtils; import android.util.ArrayMap; +import android.util.Xml; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; import android.util.proto.WireTypeMismatchException; +import com.android.internal.util.XmlUtils; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParserException; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -40,6 +48,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; +import java.util.Objects; /** * Describes information related to an application process's startup. @@ -215,6 +224,11 @@ public final class ApplicationStartInfo implements Parcelable { private int mDefiningUid; /** + * @see #getPackageName + */ + private String mPackageName; + + /** * @see #getProcessName */ private String mProcessName; @@ -349,6 +363,14 @@ public final class ApplicationStartInfo implements Parcelable { } /** + * @see #getPackageName + * @hide + */ + public void setPackageName(final String packageName) { + mPackageName = intern(packageName); + } + + /** * @see #getProcessName * @hide */ @@ -461,6 +483,15 @@ public final class ApplicationStartInfo implements Parcelable { } /** + * Name of first package running in this process; + * + * @hide + */ + public String getPackageName() { + return mPackageName; + } + + /** * The actual process name it was running with. * * <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p> @@ -555,6 +586,7 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeInt(mRealUid); dest.writeInt(mPackageUid); dest.writeInt(mDefiningUid); + dest.writeString(mPackageName); dest.writeString(mProcessName); dest.writeInt(mReason); dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size()); @@ -579,6 +611,7 @@ public final class ApplicationStartInfo implements Parcelable { mRealUid = other.mRealUid; mPackageUid = other.mPackageUid; mDefiningUid = other.mDefiningUid; + mPackageName = other.mPackageName; mProcessName = other.mProcessName; mReason = other.mReason; mStartupTimestampsNs = other.mStartupTimestampsNs; @@ -593,6 +626,7 @@ public final class ApplicationStartInfo implements Parcelable { mRealUid = in.readInt(); mPackageUid = in.readInt(); mDefiningUid = in.readInt(); + mPackageName = intern(in.readString()); mProcessName = intern(in.readString()); mReason = in.readInt(); int starupTimestampCount = in.readInt(); @@ -624,6 +658,11 @@ public final class ApplicationStartInfo implements Parcelable { } }; + private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS = "timestamps"; + private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp"; + private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key"; + private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts"; + /** * Write to a protocol buffer output stream. Protocol buffer message definition at {@link * android.app.ApplicationStartInfoProto} @@ -644,9 +683,22 @@ public final class ApplicationStartInfo implements Parcelable { if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) { ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream(); ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes); - timestampsOut.writeObject(mStartupTimestampsNs); + TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut); + serializer.startDocument(null, true); + serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS); + for (int i = 0; i < mStartupTimestampsNs.size(); i++) { + serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP); + serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY, + mStartupTimestampsNs.keyAt(i)); + serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS, + mStartupTimestampsNs.valueAt(i)); + serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP); + } + serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS); + serializer.endDocument(); proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS, timestampsBytes.toByteArray()); + timestampsOut.close(); } proto.write(ApplicationStartInfoProto.START_TYPE, mStartType); if (mStartIntent != null) { @@ -697,7 +749,24 @@ public final class ApplicationStartInfo implements Parcelable { ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes( ApplicationStartInfoProto.STARTUP_TIMESTAMPS)); ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes); - mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject(); + mStartupTimestampsNs = new ArrayMap<Integer, Long>(); + try { + TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn); + XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS); + int depth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, depth)) { + if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) { + int key = parser.getAttributeInt(null, + PROTO_SERIALIZER_ATTRIBUTE_KEY); + long ts = parser.getAttributeLong(null, + PROTO_SERIALIZER_ATTRIBUTE_TS); + mStartupTimestampsNs.put(key, ts); + } + } + } catch (XmlPullParserException e) { + // Timestamps lost + } + timestampsIn.close(); break; case (int) ApplicationStartInfoProto.START_TYPE: mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE); @@ -734,6 +803,7 @@ public final class ApplicationStartInfo implements Parcelable { .append(" definingUid=").append(mDefiningUid) .append(" user=").append(UserHandle.getUserId(mPackageUid)) .append('\n') + .append(" package=").append(mPackageName) .append(" process=").append(mProcessName) .append(" startupState=").append(mStartupState) .append(" reason=").append(reasonToString(mReason)) @@ -782,4 +852,35 @@ public final class ApplicationStartInfo implements Parcelable { default -> ""; }; } + + /** @hide */ + @Override + public boolean equals(@Nullable Object other) { + if (other == null || !(other instanceof ApplicationStartInfo)) { + return false; + } + final ApplicationStartInfo o = (ApplicationStartInfo) other; + return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid + && mDefiningUid == o.mDefiningUid && mReason == o.mReason + && mStartupState == o.mStartupState && mStartType == o.mStartType + && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName) + && timestampsEquals(o); + } + + @Override + public int hashCode() { + return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState, + mStartType, mLaunchMode, mProcessName, + mStartupTimestampsNs); + } + + private boolean timestampsEquals(@NonNull ApplicationStartInfo other) { + if (mStartupTimestampsNs == null && other.mStartupTimestampsNs == null) { + return true; + } + if (mStartupTimestampsNs == null || other.mStartupTimestampsNs == null) { + return false; + } + return mStartupTimestampsNs.equals(other.mStartupTimestampsNs); + } } diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 60b34cdc1b8a..3b83024d536b 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -95,6 +95,34 @@ interface IUiModeManager { int getNightModeCustomType(); /** + * Overlays current Night Mode value. + * {@code attentionModeThemeOverlayType}. + * + * @param attentionModeThemeOverlayType + * @hide + */ + @EnforcePermission("MODIFY_DAY_NIGHT_MODE") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)") + void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType); + + + /** + * Returns current Attention Mode overlay type. + * <p> + * returns + * <ul> + * <li>{@link #MODE_ATTENTION_OFF}</li> + * <li>{@link #MODE_ATTENTION_NIGHT}</li> + * <li>{@link #MODE_ATTENTION_DAY}</li> + * </ul> + * </p> + * @hide + */ + @EnforcePermission("MODIFY_DAY_NIGHT_MODE") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)") + int getAttentionModeThemeOverlay(); + + /** * Sets the dark mode for the given application. This setting is persisted and will override the * system configuration for this application. * 1 - notnight mode diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 476232cb40b3..ed0cfbe3d9c3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5652,7 +5652,7 @@ public class Notification implements Parcelable pillColor = Colors.flattenAlpha( getColors(p).getTertiaryFixedDimAccentColor(), bgColor); textColor = Colors.flattenAlpha( - getColors(p).getOnTertiaryAccentTextColor(), pillColor); + getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 0ccb9cddf58d..a27132872521 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; @@ -265,6 +266,60 @@ public class UiModeManager { */ public static final int MODE_NIGHT_YES = 2; + /** @hide */ + @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = { + MODE_ATTENTION_THEME_OVERLAY_OFF, + MODE_ATTENTION_THEME_OVERLAY_NIGHT, + MODE_ATTENTION_THEME_OVERLAY_DAY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttentionModeThemeOverlayType {} + + /** @hide */ + @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = { + MODE_ATTENTION_THEME_OVERLAY_OFF, + MODE_ATTENTION_THEME_OVERLAY_NIGHT, + MODE_ATTENTION_THEME_OVERLAY_DAY, + MODE_ATTENTION_THEME_OVERLAY_UNKNOWN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AttentionModeThemeOverlayReturnType {} + + /** + * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link + * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi + public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; + + /** + * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link + * #getAttentionModeThemeOverlay()}: Maintains night mode always on. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi + public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; + + /** + * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link + * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light). + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi + public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; + + /** + * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi + public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; + /** * Granular types for {@link #setNightModeCustomType(int)} * @hide @@ -733,6 +788,55 @@ public class UiModeManager { } /** + * Overlays current Attention mode Night Mode overlay. + * {@code attentionModeThemeOverlayType}. + * + * @throws IllegalArgumentException if passed an unsupported type to + * {@code AttentionModeThemeOverlayType}. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public void setAttentionModeThemeOverlay( + @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { + if (sGlobals != null) { + try { + sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the currently configured Attention Mode theme overlay. + * <p> + * May be one of: + * <ul> + * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li> + * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li> + * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li> + * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li> + * </ul> + * </p> + * + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + @TestApi + @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() { + if (sGlobals != null) { + try { + return sGlobals.mService.getAttentionModeThemeOverlay(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN; + } + + /** * Sets and persist the night mode for this application. * <p> * The mode can be one of: diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java index f94987e8495a..5ab7991c6326 100644 --- a/core/java/android/app/ambientcontext/AmbientContextEvent.java +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -89,8 +89,11 @@ public final class AmbientContextEvent implements Parcelable { */ public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name"; - /** Default value for the rate per minute data field. */ - private static final int RATE_PER_MINUTE_UNKNOWN = -1; + /** + * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown. + */ + @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE) + public static final int RATE_PER_MINUTE_UNKNOWN = -1; /** @hide */ @IntDef(prefix = { "EVENT_" }, value = { @@ -636,10 +639,10 @@ public final class AmbientContextEvent implements Parcelable { } @DataClass.Generated( - time = 1704895515931L, + time = 1705575046107L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", - inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\nprivate static final int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 32ecb58ce241..1f25fd039dd8 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -80,7 +80,7 @@ interface IPackageInstaller { long timeout); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})") - void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags); + void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 433226413917..f0efed97d8ed 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2352,7 +2352,6 @@ public class PackageInstaller { * communicated. * * @param statusReceiver Callback used to notify when the operation is completed. - * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}. * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not * available to the caller or isn't archived. */ @@ -2360,12 +2359,11 @@ public class PackageInstaller { Manifest.permission.DELETE_PACKAGES, Manifest.permission.REQUEST_DELETE_PACKAGES}) @FlaggedApi(Flags.FLAG_ARCHIVING) - public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver, - @DeleteFlags int flags) + public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver) throws PackageManager.NameNotFoundException { try { mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver, - new UserHandle(mUserId), flags); + new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); } catch (RemoteException e) { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8e5e8250c85d..aabbe698703c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2596,7 +2596,6 @@ public abstract class PackageManager { DELETE_SYSTEM_APP, DELETE_DONT_KILL_APP, DELETE_CHATTY, - DELETE_SHOW_DIALOG, }) @Retention(RetentionPolicy.SOURCE) public @interface DeleteFlags {} @@ -2649,12 +2648,6 @@ public abstract class PackageManager { public static final int DELETE_ARCHIVE = 0x00000010; /** - * Show a confirmation dialog to the user when app is being deleted. - */ - @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING) - public static final int DELETE_SHOW_DIALOG = 0x00000020; - - /** * Flag parameter for {@link #deletePackage} to indicate that package deletion * should be chatty. * diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index c7797c719e2c..7c5d3054c945 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -70,4 +70,11 @@ flag { namespace: "profile_experiences" description: "Add support for Private Space in resolver sheet" bug: "307515485" +} + +flag { + name: "move_quiet_mode_operations_to_separate_thread" + namespace: "profile_experiences" + description: "Move the quiet mode operations, happening on a background thread today, to a separate thread." + bug: "320483504" }
\ No newline at end of file diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java index 088949e7eec2..f4312a905110 100644 --- a/core/java/android/content/res/FontScaleConverter.java +++ b/core/java/android/content/res/FontScaleConverter.java @@ -17,7 +17,9 @@ package android.content.res; +import android.annotation.AnyThread; import android.annotation.FlaggedApi; +import android.annotation.Nullable; /** * A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a @@ -40,4 +42,35 @@ public interface FontScaleConverter { * Converts a dimension in "dp" back to "sp". */ float convertDpToSp(float dp); + + /** + * Returns true if non-linear font scaling curves would be in effect for the given scale, false + * if the scaling would follow a linear curve or for no scaling. + * + * <p>Example usage: {@code + * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)} + */ + @AnyThread + static boolean isNonLinearFontScalingActive(float fontScale) { + return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale); + } + + /** + * Finds a matching FontScaleConverter for the given fontScale factor. + * + * Generally you shouldn't need this; you can use {@link + * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do + * the scaling conversion for you. Dimens and resources loaded from XML will also be + * automatically converted. But for UI frameworks or other situations where you need to do the + * conversion without an Android Context, you can use this method. + * + * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. + * + * @return a converter for the given scale, or null if non-linear scaling should not be used. + */ + @Nullable + @AnyThread + static FontScaleConverter forScale(float fontScale) { + return FontScaleConverterFactory.forScale(fontScale); + } } diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java index 5d31cc0f0243..cbe4c62d7069 100644 --- a/core/java/android/content/res/FontScaleConverterFactory.java +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -17,7 +17,6 @@ package android.content.res; import android.annotation.AnyThread; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.util.MathUtils; @@ -32,8 +31,9 @@ import com.android.internal.annotations.VisibleForTesting; * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the * scaling conversion for you. But for UI frameworks or other situations where you need to do the * conversion without an Android Context, you can use this class. + * + * @hide */ -@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) public class FontScaleConverterFactory { private static final float SCALE_KEY_MULTIPLIER = 100f; @@ -124,7 +124,6 @@ public class FontScaleConverterFactory { * <p>Example usage: * <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code> */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @AnyThread public static boolean isNonLinearFontScalingActive(float fontScale) { return fontScale >= sMinScaleBeforeCurvesApplied; @@ -137,7 +136,6 @@ public class FontScaleConverterFactory { * * @return a converter for the given scale, or null if non-linear scaling should not be used. */ - @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC) @Nullable @AnyThread public static FontScaleConverter forScale(float fontScale) { diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a0f4d8df0bd5..c0424dbeb813 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -16,6 +16,7 @@ package android.hardware.biometrics; +import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -25,6 +26,7 @@ import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT; import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT; import android.annotation.CallbackExecutor; +import android.annotation.DrawableRes; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; @@ -33,6 +35,7 @@ import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.content.Context; import android.content.DialogInterface; +import android.graphics.Bitmap; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Binder; @@ -160,6 +163,45 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Optional: Sets the drawable resource of the logo that will be shown on the prompt. + * + * <p> Note that using this method is not recommended in most scenarios because the calling + * application's icon will be used by default. Setting the logo is intended for large + * bundled applications that perform a wide range of functions and need to show distinct + * icons for each function. + * + * @param logoRes A drawable resource of the logo that will be shown on the prompt. + * @return This builder. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @NonNull + public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) { + mPromptInfo.setLogoRes(logoRes); + return this; + } + + /** + * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt. + * + * <p> Note that using this method is not recommended in most scenarios because the calling + * application's icon will be used by default. Setting the logo is intended for large + * bundled applications that perform a wide range of functions and need to show distinct + * icons for each function. + * + * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt. + * @return This builder. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @NonNull + public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) { + mPromptInfo.setLogoBitmap(logoBitmap); + return this; + } + + + /** * Required: Sets the title that will be shown on the prompt. * @param title The title to display. * @return This builder. @@ -676,6 +718,34 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Gets the drawable resource of the logo for the prompt, as set by + * {@link Builder#setLogo(int)}. Currently for system applications use only. + * + * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @DrawableRes + public int getLogoRes() { + return mPromptInfo.getLogoRes(); + } + + /** + * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for + * system applications use only. + * + * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set. + */ + @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) + @RequiresPermission(MANAGE_BIOMETRIC_DIALOG) + @Nullable + public Bitmap getLogoBitmap() { + return mPromptInfo.getLogoBitmap(); + } + + + + /** * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}. * @return The title of the prompt, which is guaranteed to be non-null. */ diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index c73ebd4dbe76..d788b37c781d 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -16,8 +16,10 @@ package android.hardware.biometrics; +import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -30,6 +32,8 @@ import java.util.List; */ public class PromptInfo implements Parcelable { + @DrawableRes private int mLogoRes = -1; + @Nullable private Bitmap mLogoBitmap; @NonNull private CharSequence mTitle; private boolean mUseDefaultTitle; @Nullable private CharSequence mSubtitle; @@ -56,6 +60,8 @@ public class PromptInfo implements Parcelable { } PromptInfo(Parcel in) { + mLogoRes = in.readInt(); + mLogoBitmap = in.readTypedObject(Bitmap.CREATOR); mTitle = in.readCharSequence(); mUseDefaultTitle = in.readBoolean(); mSubtitle = in.readCharSequence(); @@ -98,6 +104,8 @@ public class PromptInfo implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mLogoRes); + dest.writeTypedObject(mLogoBitmap, 0); dest.writeCharSequence(mTitle); dest.writeBoolean(mUseDefaultTitle); dest.writeCharSequence(mSubtitle); @@ -156,9 +164,30 @@ public class PromptInfo implements Parcelable { } return false; } + + /** + * Returns whether MANAGE_BIOMETRIC_DIALOG is contained. + */ + public boolean containsManageBioApiConfigurations() { + if (mLogoRes != -1) { + return true; + } else if (mLogoBitmap != null) { + return true; + } + return false; + } // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java) // Setters + public void setLogoRes(@DrawableRes int logoRes) { + mLogoRes = logoRes; + checkOnlyOneLogoSet(); + } + + public void setLogoBitmap(@NonNull Bitmap logoBitmap) { + mLogoBitmap = logoBitmap; + checkOnlyOneLogoSet(); + } public void setTitle(CharSequence title) { mTitle = title; @@ -244,6 +273,14 @@ public class PromptInfo implements Parcelable { } // Getters + @DrawableRes + public int getLogoRes() { + return mLogoRes; + } + + public Bitmap getLogoBitmap() { + return mLogoBitmap; + } public CharSequence getTitle() { return mTitle; @@ -337,4 +374,11 @@ public class PromptInfo implements Parcelable { public boolean isShowEmergencyCallButton() { return mShowEmergencyCallButton; } + + private void checkOnlyOneLogoSet() { + if (mLogoRes != -1 && mLogoBitmap != null) { + throw new IllegalStateException( + "Exclusively one of logo resource or logo bitmap can be set"); + } + } } diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/core/java/android/hardware/radio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 25fba60b9bb5..b9bb059bef42 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -17,9 +17,11 @@ package android.os; import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC; +import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API; import android.Manifest.permission; import android.annotation.FlaggedApi; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; @@ -236,6 +238,31 @@ public class BatteryManager { public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4 + // values for "battery part status" property + /** + * Battery part status is not supported. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_UNSUPPORTED = 0; + + /** + * Battery is the original device battery. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_ORIGINAL = 1; + + /** + * Battery has been replaced. + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int PART_STATUS_REPLACED = 2; + /** @hide */ @SuppressLint("UnflaggedApi") // TestApi without associated feature. @TestApi @@ -366,6 +393,32 @@ public class BatteryManager { @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; + /** + * Battery part serial number. + * + * <p class="note"> + * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. + * + * @hide + */ + @RequiresPermission(permission.BATTERY_STATS) + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; + + /** + * Battery part status from a BATTERY_PART_STATUS_* value. + * + * <p class="note"> + * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. + * + * @hide + */ + @RequiresPermission(permission.BATTERY_STATS) + @SystemApi + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public static final int BATTERY_PROPERTY_PART_STATUS = 12; + private final Context mContext; private final IBatteryStats mBatteryStats; private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar; @@ -431,6 +484,25 @@ public class BatteryManager { } /** + * Same as queryProperty, but for strings. + */ + private String queryStringProperty(int id) { + if (mBatteryPropertiesRegistrar == null) { + return null; + } + + try { + BatteryProperty prop = new BatteryProperty(); + if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) { + return prop.getString(); + } + return null; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Return the value of a battery property of integer type. * * @param id identifier of the requested property @@ -464,6 +536,21 @@ public class BatteryManager { } /** + * Return the value of a battery property of String type. If the + * platform does not provide the property queried, this value will + * be null. + * + * @param id identifier of the requested property. + * + * @return the property value, or null if not supported. + */ + @Nullable + @FlaggedApi(FLAG_BATTERY_PART_STATUS_API) + public String getStringProperty(int id) { + return queryStringProperty(id); + } + + /** * Return true if the plugType given is wired * @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB}, * or {@link #BATTERY_PLUGGED_WIRELESS} diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java index b40988a938bc..464577f58241 100644 --- a/core/java/android/os/BatteryProperty.java +++ b/core/java/android/os/BatteryProperty.java @@ -28,12 +28,14 @@ import android.os.Parcelable; */ public class BatteryProperty implements Parcelable { private long mValueLong; + private String mValueString; /** * @hide */ public BatteryProperty() { mValueLong = Long.MIN_VALUE; + mValueString = null; } /** @@ -46,14 +48,23 @@ public class BatteryProperty implements Parcelable { /** * @hide */ + public String getString() { + return mValueString; + } + + /** + * @hide + */ public void setLong(long val) { mValueLong = val; } - /* - * Parcel read/write code must be kept in sync with - * frameworks/native/services/batteryservice/BatteryProperty.cpp + /** + * @hide */ + public void setString(String val) { + mValueString = val; + } private BatteryProperty(Parcel p) { readFromParcel(p); @@ -61,10 +72,12 @@ public class BatteryProperty implements Parcelable { public void readFromParcel(Parcel p) { mValueLong = p.readLong(); + mValueString = p.readString8(); } public void writeToParcel(Parcel p, int flags) { p.writeLong(mValueLong); + p.writeString8(mValueString); } public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 0db90bff48fd..82518bfbfd8d 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -106,3 +106,11 @@ flag { bug: "305311707" is_fixed_read_only: true } + +flag { + name: "battery_part_status_api" + namespace: "phoenix" + description: "Feature flag for adding Health HAL v3 APIs." + is_fixed_read_only: true + bug: "309792384" +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 39b6aeb814f6..db8f52c307a1 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -86,3 +86,10 @@ flag { description: "This flag is used to enabled the Wallet Role for all users on the device" bug: "283989236" } + +flag { + name: "runtime_permission_appops_mapping" + namespace: "permissions" + description: "Use runtime permission state to determine appop state" + bug: "266164193" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7d84bb390853..58159c2b5693 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3176,15 +3176,7 @@ public final class Settings { } public void destroy() { - try { - // If this process is the system server process, mArray is the same object as - // the memory int array kept inside SettingsProvider, so skipping the close() - if (!Settings.isInSystemServer() && !mArray.isClosed()) { - mArray.close(); - } - } catch (IOException e) { - Log.e(TAG, "Error closing backing array", e); - } + maybeCloseGenerationArray(mArray); } @Override @@ -3197,6 +3189,21 @@ public final class Settings { } } + private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) { + if (array == null) { + return; + } + try { + // If this process is the system server process, the MemoryIntArray received from Parcel + // is the same object as the one kept inside SettingsProvider, so skipping the close(). + if (!Settings.isInSystemServer() && !array.isClosed()) { + array.close(); + } + } catch (IOException e) { + Log.e(TAG, "Error closing the generation tracking array", e); + } + } + private static final class ContentProviderHolder { private final Object mLock = new Object(); @@ -3496,6 +3503,8 @@ public final class Settings { mGenerationTrackers.put(name, new GenerationTracker(name, array, index, generation, mGenerationTrackerErrorHandler)); + } else { + maybeCloseGenerationArray(array); } } if (mGenerationTrackers.get(name) != null @@ -3733,6 +3742,8 @@ public final class Settings { new GenerationTracker(prefix, array, index, generation, mGenerationTrackerErrorHandler)); currentGeneration = generation; + } else { + maybeCloseGenerationArray(array); } } if (mGenerationTrackers.get(prefix) != null && currentGeneration @@ -7310,6 +7321,28 @@ public final class Settings { "bluetooth_le_broadcast_app_source_name"; /** + * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve + * compatibility. + * + * <ul> + * <li>0 = false + * <li>1 = true + * </ul> + * + * @hide + */ + public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY = + "bluetooth_le_broadcast_improve_compatibility"; + + /** + * This is used by LocalBluetoothLeBroadcast to store the fallback active device address. + * + * @hide + */ + public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS = + "bluetooth_le_broadcast_fallback_active_device_address"; + + /** * Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device * speaker. * <ul> diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index 8b4a99e38299..d5ac7a7de461 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -28,5 +28,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 30524a1132fa..1994058441d5 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -53,7 +53,7 @@ flag { flag { name: "frp_enforcement" - namespace: "android_hw_security" + namespace: "hardware_backed_security" description: "This flag controls whether PDB enforces FRP" bug: "290312729" is_fixed_read_only: true diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index ac5eb3cbeeaa..b268c2edd9a7 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -671,9 +671,18 @@ public class MeasuredParagraph { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. if (builder == null) { - mWholeWidth += paint.getTextRunAdvances( - mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, - mWidths.getRawArray(), start); + // For the compatibility reasons, the letter spacing should not be dropped at the + // left and right edge. + int oldFlag = paint.getFlags(); + paint.setFlags(paint.getFlags() + | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + mWholeWidth += paint.getTextRunAdvances( + mCopiedBuffer, start, end - start, start, end - start, + false /* isRtl */, mWidths.getRawArray(), start); + } finally { + paint.setFlags(oldFlag); + } } else { builder.appendStyleRun(paint, config, end - start, false /* isRtl */); } @@ -690,9 +699,16 @@ public class MeasuredParagraph { final boolean isRtl = (level & 0x1) != 0; if (builder == null) { final int levelLength = levelEnd - levelStart; - mWholeWidth += paint.getTextRunAdvances( - mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, - isRtl, mWidths.getRawArray(), levelStart); + int oldFlag = paint.getFlags(); + paint.setFlags(paint.getFlags() + | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + mWholeWidth += paint.getTextRunAdvances( + mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, + isRtl, mWidths.getRawArray(), levelStart); + } finally { + paint.setFlags(oldFlag); + } } else { builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl); } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 135935cb0632..2175b47e149e 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -291,6 +291,97 @@ public class TextLine { } /** + * Returns the run flag of at the given BiDi run. + * + * @param bidiRunIndex a BiDi run index. + * @return a run flag of the given BiDi run. + */ + @VisibleForTesting + public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) { + if (bidiRunCount == 1) { + // Easy case. If there is only single run, it is most left and most right run. + return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) { + // Easy case. If the given run is the middle of the line, it is not the most left or + // the most right run. + return 0; + } + + int runFlag = 0; + // For the historical reasons, the BiDi implementation of Android works differently + // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but + // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of + // mDirections is located the most left of the line if the paragraph direction is LTR. + // If the paragraph direction is RTL, the first BiDi run is located the most right of + // the line. + if (bidiRunIndex == 0) { + if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) { + runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } else { + runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } + if (bidiRunIndex == (bidiRunCount - 1)) { + if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) { + runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } else { + runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } + return runFlag; + } + + /** + * Resolve the runFlag for the inline span range. + * + * @param runFlag the runFlag of the current BiDi run. + * @param isRtlRun true for RTL run, false for LTR run. + * @param runStart the inclusive BiDi run start offset. + * @param runEnd the exclusive BiDi run end offset. + * @param spanStart the inclusive span start offset. + * @param spanEnd the exclusive span end offset. + * @return the resolved runFlag. + */ + @VisibleForTesting + public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart, + int runEnd, int spanStart, int spanEnd) { + if (runFlag == 0) { + // Easy case. If the run is in the middle of the line, any inline span is also in the + // middle of the line. + return 0; + } + int localRunFlag = runFlag; + if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) { + if (isRtlRun) { + if (spanEnd != runEnd) { + // In the RTL context, the last run is the most left run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } else { // LTR + if (spanStart != runStart) { + // In the LTR context, the first run is the most left run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE; + } + } + } + if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) { + if (isRtlRun) { + if (spanStart != runStart) { + // In the RTL context, the start of the run is the most right run. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } else { // LTR + if (spanEnd != runEnd) { + // In the LTR context, the last run is the most right position. + localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE; + } + } + } + return localRunFlag; + } + + /** * Renders the TextLine. * * @param c the canvas to render on @@ -308,11 +399,13 @@ public class TextLine { final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); + int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom, - runIndex != (runCount - 1) || j != mLen); + runIndex != (runCount - 1) || j != mLen, runFlag); if (j != runLimit) { // charAt(j) == TAB_CHAR h = mDir * nextTab(h * mDir); @@ -371,11 +464,12 @@ public class TextLine { final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal, - runIndex != (runCount - 1) || j != mLen); + runIndex != (runCount - 1) || j != mLen, runFlag); if (j != runLimit) { // charAt(j) == TAB_CHAR horizontal = mDir * nextTab(horizontal * mDir); @@ -441,11 +535,13 @@ public class TextLine { } float h = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + final int runCount = mDirections.getRunCount(); + for (int runIndex = 0; runIndex < runCount; runIndex++) { final int runStart = mDirections.getRunStart(runIndex); if (runStart > mLen) break; final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { @@ -455,16 +551,16 @@ public class TextLine { if (targetIsInThisSegment && sameDirection) { return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null, - 0, h, lineInfo); + 0, h, lineInfo, runFlag); } final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds, - null, 0, h, lineInfo); + null, 0, h, lineInfo, runFlag); h += sameDirection ? segmentWidth : -segmentWidth; if (targetIsInThisSegment) { return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0, - h, lineInfo); + h, lineInfo, runFlag); } if (j != runLimit) { // charAt(j) == TAB_CHAR @@ -543,20 +639,21 @@ public class TextLine { + "result, needed: " + mLen + " had: " + advances.length); } float h = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + final int runCount = mDirections.getRunCount(); + for (int runIndex = 0; runIndex < runCount; runIndex++) { final int runStart = mDirections.getRunStart(runIndex); if (runStart > mLen) break; final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) { if (j == runLimit || charAt(j) == TAB_CHAR) { final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; - final float segmentWidth = measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0, - null); + null, runFlag); final float oldh = h; h += sameDirection ? segmentWidth : -segmentWidth; @@ -608,11 +705,13 @@ public class TextLine { } float horizontal = 0; - for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) { + final int runCount = mDirections.getRunCount(); + for (int runIndex = 0; runIndex < runCount; runIndex++) { final int runStart = mDirections.getRunStart(runIndex); if (runStart > mLen) break; final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen); final boolean runIsRtl = mDirections.isRunRtl(runIndex); + final int runFlag = calculateRunFlag(runIndex, runCount, mDir); int segStart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) { @@ -629,7 +728,7 @@ public class TextLine { final float previousSegEndHorizontal = measurement[segStart]; final float width = measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart, - 0, null); + 0, null, runFlag); horizontal += sameDirection ? width : -width; float currHorizontal = sameDirection ? oldHorizontal : horizontal; @@ -686,22 +785,24 @@ public class TextLine { * @param y the baseline * @param bottom the bottom of the line * @param needWidth true if the width value is required. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float drawRun(Canvas c, int start, int limit, boolean runIsRtl, float x, int top, int y, int bottom, - boolean needWidth) { + boolean needWidth, int runFlag) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); handleRun(start, limit, limit, runIsRtl, c, null, x + w, top, - y, bottom, null, null, false, null, 0, null); + y, bottom, null, null, false, null, 0, null, runFlag); return w; } return handleRun(start, limit, limit, runIsRtl, c, null, x, top, - y, bottom, null, null, needWidth, null, 0, null); + y, bottom, null, null, needWidth, null, 0, null, runFlag); } /** @@ -718,19 +819,21 @@ public class TextLine { * @param advancesIndex the start index to fill in the advance information. * @param x horizontal offset of the run. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @return the signed width from the start of the run to the leading edge * of the character at offset, based on the run (not paragraph) direction */ private float measureRun(int start, int offset, int limit, boolean runIsRtl, @Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances, - int advancesIndex, float x, @Nullable LineInfo lineInfo) { + int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) { if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi, - drawBounds, true, advances, advancesIndex, lineInfo); + drawBounds, true, advances, advancesIndex, lineInfo, runFlag); } return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds, - true, advances, advancesIndex, lineInfo); + true, advances, advancesIndex, lineInfo, runFlag); } /** @@ -742,21 +845,23 @@ public class TextLine { * @param runIsRtl true if the run is right-to-left * @param x the position of the run that is closest to the leading margin * @param needWidth true if the width value is required. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run, based on the paragraph direction. * Only valid if needWidth is true. */ private float shapeRun(TextShaper.GlyphsConsumer consumer, int start, - int limit, boolean runIsRtl, float x, boolean needWidth) { + int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) { if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) { - float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null); + float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null, + runFlag); handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null, - false, null, 0, null); + false, null, 0, null, runFlag); return w; } return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null, - needWidth, null, 0, null); + needWidth, null, 0, null, runFlag); } @@ -1160,6 +1265,7 @@ public class TextLine { * @param advances receives the advance information about the requested run, can be null. * @param advancesIndex the start index to fill in the advance information. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -1168,8 +1274,8 @@ public class TextLine { Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset, @Nullable ArrayList<DecorationInfo> decorations, - @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) { - + @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo, + int runFlag) { if (mIsJustifying) { wp.setWordSpacing(mAddedWidthForJustify); } @@ -1187,7 +1293,16 @@ public class TextLine { } float totalWidth = 0; - + if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) { + wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE); + } else { + wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE); + } + if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) { + wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE); + } else { + wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE); + } final int numDecorations = decorations == null ? 0 : decorations.size(); if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0 || numDecorations != 0 || runIsRtl))) { @@ -1419,6 +1534,7 @@ public class TextLine { * @param advances receives the advance information about the requested run, can be null. * @param advancesIndex the start index to fill in the advance information. * @param lineInfo an optional output parameter for filling line information. + * @param runFlag the run flag to be applied for this run. * @return the signed width of the run based on the run direction; only * valid if needWidth is true */ @@ -1426,7 +1542,8 @@ public class TextLine { int limit, boolean runIsRtl, Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth, - @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) { + @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo, + int runFlag) { if (measureLimit < start || measureLimit > limit) { throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of " @@ -1473,7 +1590,7 @@ public class TextLine { wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit())); return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top, y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances, - advancesIndex, lineInfo); + advancesIndex, lineInfo, runFlag); } // Shaping needs to take into account context up to metric boundaries, @@ -1554,6 +1671,9 @@ public class TextLine { // and use. activePaint.set(wp); } else if (!equalAttributes(wp, activePaint)) { + final int spanRunFlag = resolveRunFlagForSubSequence( + runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd); + // The style of the present chunk of text is substantially different from the // style of the previous chunk. We need to handle the active piece of text // and restart with the present chunk. @@ -1565,7 +1685,7 @@ public class TextLine { consumer, x, top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations, - advances, advancesIndex + activeStart - start, lineInfo); + advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag); activeStart = j; activePaint.set(wp); @@ -1585,6 +1705,9 @@ public class TextLine { mDecorations.add(copy); } } + + final int spanRunFlag = resolveRunFlagForSubSequence( + runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd); // Handle the final piece of text. activePaint.setStartHyphenEdit( adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit())); @@ -1593,7 +1716,7 @@ public class TextLine { x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x, top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit, Math.min(activeEnd, mlimit), mDecorations, - advances, advancesIndex + activeStart - start, lineInfo); + advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag); } return x - originalX; @@ -1614,7 +1737,6 @@ public class TextLine { */ private void drawTextRun(Canvas c, TextPaint wp, int start, int end, int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { - if (mCharsValid) { int count = end - start; int contextCount = contextEnd - contextStart; diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java index b8daace30d0a..baece75cbf09 100644 --- a/core/java/android/tracing/transition/TransitionDataSource.java +++ b/core/java/android/tracing/transition/TransitionDataSource.java @@ -16,6 +16,7 @@ package android.tracing.transition; +import android.tracing.perfetto.CreateTlsStateArgs; import android.tracing.perfetto.DataSource; import android.tracing.perfetto.DataSourceInstance; import android.tracing.perfetto.FlushCallbackArguments; @@ -23,10 +24,14 @@ import android.tracing.perfetto.StartCallbackArguments; import android.tracing.perfetto.StopCallbackArguments; import android.util.proto.ProtoInputStream; +import java.util.HashMap; +import java.util.Map; + /** * @hide */ -public class TransitionDataSource extends DataSource { +public class TransitionDataSource + extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> { public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition"; private final Runnable mOnStartStaticCallback; @@ -41,6 +46,15 @@ public class TransitionDataSource extends DataSource { } @Override + protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) { + return new TlsState(); + } + + public class TlsState { + public final Map<String, Integer> handlerMapping = new HashMap<>(); + } + + @Override public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { return new DataSourceInstance(this, instanceIndex) { @Override diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 785055441d59..ba7874eb2d21 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -39,8 +39,10 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.view.flags.Flags; import dalvik.system.CloseGuard; +import dalvik.system.VMRuntime; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -101,6 +103,10 @@ public class Surface implements Parcelable { long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); private static native void nativeDestroy(long nativeObject); + // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen + // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance. + private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000; + public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR = new Parcelable.Creator<Surface>() { @Override @@ -331,6 +337,7 @@ public class Surface implements Parcelable { */ @UnsupportedAppUsage public Surface() { + registerNativeMemoryUsage(); } /** @@ -343,6 +350,7 @@ public class Surface implements Parcelable { */ public Surface(@NonNull SurfaceControl from) { copyFrom(from); + registerNativeMemoryUsage(); } /** @@ -370,6 +378,7 @@ public class Surface implements Parcelable { mName = surfaceTexture.toString(); setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture)); } + registerNativeMemoryUsage(); } /* called from android_view_Surface_createFromIGraphicBufferProducer() */ @@ -378,6 +387,7 @@ public class Surface implements Parcelable { synchronized (mLock) { setNativeObjectLocked(nativeObject); } + registerNativeMemoryUsage(); } @Override @@ -389,6 +399,7 @@ public class Surface implements Parcelable { release(); } finally { super.finalize(); + freeNativeMemoryUsage(); } } @@ -1243,4 +1254,16 @@ public class Surface implements Parcelable { return mIsWideColorGamut; } } + + private static void registerNativeMemoryUsage() { + if (Flags.enableSurfaceNativeAllocRegistration()) { + VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); + } + } + + private static void freeNativeMemoryUsage() { + if (Flags.enableSurfaceNativeAllocRegistration()) { + VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES); + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 34b467008232..57174de8a62c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -26,6 +26,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -1010,8 +1011,10 @@ public final class ViewRootImpl implements ViewParent, // Used to check if there were any view invalidations in // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). private boolean mHasInvalidation = false; - // Used to check if it is in the touch boosting period. + // Used to check if it is in the frame rate boosting period. private boolean mIsFrameRateBoosting = false; + // Used to check if it is in touch boosting period. + private boolean mIsTouchBoosting = false; // Used to check if there is a message in the message queue // for idleness handling. private boolean mHasIdledMessage = false; @@ -6426,11 +6429,12 @@ public final class ViewRootImpl implements ViewParent, * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). */ mIsFrameRateBoosting = false; + mIsTouchBoosting = false; setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, mLastPreferredFrameRateCategory)); break; case MSG_CHECK_INVALIDATION_IDLE: - if (!mHasInvalidation && !mIsFrameRateBoosting) { + if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) { mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; setPreferredFrameRateCategory(mPreferredFrameRateCategory); mHasIdledMessage = false; @@ -7453,7 +7457,7 @@ public final class ViewRootImpl implements ViewParent, // For the variable refresh rate project if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { // set the frame rate to the maximum value. - mIsFrameRateBoosting = true; + mIsTouchBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); } /** @@ -12202,8 +12206,16 @@ public final class ViewRootImpl implements ViewParent, return; } - int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning - ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + int frameRateCategory = mIsTouchBoosting + ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory; + + // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT + // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. + // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction + // (e.g., Window Initialization). + if (mIsFrameRateBoosting || mInsetsAnimationRunning) { + frameRateCategory = FRAME_RATE_CATEGORY_HIGH; + } try { if (mLastPreferredFrameRateCategory != frameRateCategory) { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 2b96ee6705fd..2c5fbd7f3725 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -89,4 +89,12 @@ flag { description: "Feature flag to enable a multi-instance system ui component property." bug: "262864589" is_fixed_read_only: true +} + +flag { + name: "insets_decoupled_configuration" + namespace: "windowing_frontend" + description: "Configuration decoupled from insets" + bug: "151861875" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java index c3bcfa6b2cc2..eca6f58dd50f 100644 --- a/core/java/com/android/internal/os/MonotonicClock.java +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -17,11 +17,11 @@ package com.android.internal.os; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -49,20 +49,27 @@ public class MonotonicClock { private final AtomicFile mFile; private final Clock mClock; - private long mTimeshift; + private final long mTimeshift; public static final long UNDEFINED = -1; public MonotonicClock(File file) { - mFile = new AtomicFile(file); - mClock = Clock.SYSTEM_CLOCK; - read(); + this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK); } public MonotonicClock(long monotonicTime, @NonNull Clock clock) { + this(null, monotonicTime, clock); + } + + public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) { mClock = clock; - mFile = null; - mTimeshift = monotonicTime - mClock.elapsedRealtime(); + if (file != null) { + mFile = new AtomicFile(file); + mTimeshift = read(monotonicTime - mClock.elapsedRealtime()); + } else { + mFile = null; + mTimeshift = monotonicTime - mClock.elapsedRealtime(); + } } /** @@ -81,15 +88,16 @@ public class MonotonicClock { return mTimeshift + elapsedRealtimeMs; } - private void read() { + private long read(long defaultTimeshift) { if (!mFile.exists()) { - return; + return defaultTimeshift; } try { - readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); + return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); } catch (IOException e) { Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e); + return defaultTimeshift; } } @@ -102,18 +110,21 @@ public class MonotonicClock { return; } - try (FileOutputStream out = mFile.startWrite()) { + FileOutputStream out = null; + try { + out = mFile.startWrite(); writeXml(out, Xml.newBinarySerializer()); + mFile.finishWrite(out); } catch (IOException e) { Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); + mFile.failWrite(out); } } /** * Parses an XML file containing the persistent state of the monotonic clock. */ - @VisibleForTesting - public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { + private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { long savedTimeshift = 0; try { parser.setInput(inputStream, StandardCharsets.UTF_8.name()); @@ -128,14 +139,13 @@ public class MonotonicClock { } catch (XmlPullParserException e) { throw new IOException(e); } - mTimeshift = savedTimeshift - mClock.elapsedRealtime(); + return savedTimeshift - mClock.elapsedRealtime(); } /** * Creates an XML file containing the persistent state of the monotonic clock. */ - @VisibleForTesting - public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { + private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { serializer.setOutput(out, StandardCharsets.UTF_8.name()); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_MONOTONIC_TIME); diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java index e3bfb38802db..e419d13730c2 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java @@ -38,7 +38,7 @@ import java.util.List; public class ParsedAttributionImpl implements ParsedAttribution, Parcelable { /** Maximum amount of attributions per package */ - static final int MAX_NUM_ATTRIBUTIONS = 10000; + static final int MAX_NUM_ATTRIBUTIONS = 400; /** Tag of the attribution */ private @NonNull String tag; diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index b87027cbb6e7..419193688268 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -120,8 +120,9 @@ public class CollectionUtils { public static @NonNull <I, O> List<O> map(@Nullable List<I> cur, Function<? super I, ? extends O> f) { if (isEmpty(cur)) return Collections.emptyList(); - final ArrayList<O> result = new ArrayList<>(); - for (int i = 0; i < cur.size(); i++) { + final int size = cur.size(); + final ArrayList<O> result = new ArrayList<>(size); + for (int i = 0; i < size; i++) { result.add(f.apply(cur.get(i))); } return result; @@ -133,7 +134,7 @@ public class CollectionUtils { public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur, Function<? super I, ? extends O> f) { if (isEmpty(cur)) return emptySet(); - ArraySet<O> result = new ArraySet<>(); + ArraySet<O> result = new ArraySet<>(cur.size()); if (cur instanceof ArraySet) { ArraySet<I> arraySet = (ArraySet<I>) cur; int size = arraySet.size(); @@ -163,7 +164,7 @@ public class CollectionUtils { Function<? super I, ? extends O> f) { if (isEmpty(cur)) return Collections.emptyList(); List<O> result = null; - for (int i = 0; i < cur.size(); i++) { + for (int i = 0, size = cur.size(); i < size; i++) { O transformed = f.apply(cur.get(i)); if (transformed != null) { result = add(result, transformed); @@ -239,7 +240,7 @@ public class CollectionUtils { public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) { if (isEmpty(list)) return Collections.emptyList(); ArrayList<T> result = null; - for (int i = 0; i < list.size(); i++) { + for (int i = 0, size = list.size(); i < size; i++) { final Object item = list.get(i); if (c.isInstance(item)) { result = ArrayUtils.add(result, (T) item); @@ -273,7 +274,7 @@ public class CollectionUtils { public static @Nullable <T> T find(@Nullable List<T> items, java.util.function.Predicate<T> predicate) { if (isEmpty(items)) return null; - for (int i = 0; i < items.size(); i++) { + for (int i = 0, size = items.size(); i < size; i++) { final T item = items.get(i); if (predicate.test(item)) return item; } diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java index 04886598de35..342ba1b67e4d 100644 --- a/core/java/com/android/internal/util/RingBuffer.java +++ b/core/java/com/android/internal/util/RingBuffer.java @@ -19,7 +19,10 @@ package com.android.internal.util; import static com.android.internal.util.Preconditions.checkArgumentPositive; import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; +import java.util.function.IntFunction; +import java.util.function.Supplier; /** * A simple ring buffer structure with bounded capacity backed by an array. @@ -30,16 +33,35 @@ import java.util.Arrays; @android.ravenwood.annotation.RavenwoodKeepWholeClass public class RingBuffer<T> { + private final Supplier<T> mNewItem; // Array for storing events. private final T[] mBuffer; // Cursor keeping track of the logical end of the array. This cursor never // wraps and instead keeps track of the total number of append() operations. private long mCursor = 0; + /** + * @deprecated This uses reflection to create new instances. + * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead. + */ + @Deprecated public RingBuffer(Class<T> c, int capacity) { + this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity); + } + + private static Object createNewItem(Class c) { + try { + return c.getDeclaredConstructor().newInstance(); + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException + | InvocationTargetException e) { + return null; + } + } + + public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity"); - // Java cannot create generic arrays without a runtime hint. - mBuffer = (T[]) Array.newInstance(c, capacity); + mBuffer = newBacking.apply(capacity); + mNewItem = newItem; } public int size() { @@ -69,22 +91,11 @@ public class RingBuffer<T> { public T getNextSlot() { final int nextSlotIdx = indexOf(mCursor++); if (mBuffer[nextSlotIdx] == null) { - mBuffer[nextSlotIdx] = createNewItem(); + mBuffer[nextSlotIdx] = mNewItem.get(); } return mBuffer[nextSlotIdx]; } - /** - * @return a new object of type <T> or null if a new object could not be created. - */ - protected T createNewItem() { - try { - return (T) mBuffer.getClass().getComponentType().newInstance(); - } catch (IllegalAccessException | InstantiationException e) { - return null; - } - } - public T[] toArray() { // Only generic way to create a T[] from another T[] T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass()); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5f3f6419418a..762218be0fda 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3160,7 +3160,7 @@ types of interactions @hide --> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" - android:protectionLevel="signature|installer|role" /> + android:protectionLevel="signature|installer|module|role" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <!-- Allows interaction across profiles in the same profile group. --> @@ -5055,7 +5055,7 @@ <p>Intended for use by ROLE_ASSISTANT and signature apps only. --> <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" - android:protectionLevel="signature|role"/> + android:protectionLevel="signature|module|role"/> <!-- Must be required by a {@link android.service.autofill.AutofillService}, to ensure that only the system can bind to it. @@ -5673,7 +5673,7 @@ <!-- @SystemApi Allows an application to manage the holders of a role. @hide --> <permission android:name="android.permission.MANAGE_ROLE_HOLDERS" - android:protectionLevel="signature|installer" /> + android:protectionLevel="signature|installer|module" /> <!-- @SystemApi Allows an application to manage the holders of roles associated with default applications. @@ -5693,7 +5693,7 @@ <!-- @SystemApi Allows an application to observe role holder changes. @hide --> <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" - android:protectionLevel="signature|installer" /> + android:protectionLevel="signature|installer|module" /> <!-- Allows an application to manage the companion devices. @hide --> @@ -7896,6 +7896,26 @@ <permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" android:protectionLevel="signature|role" /> + <!-- @SystemApi + @FlaggedApi("android.app.bic_client") + Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps + for all users on device. + <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without + this permission. + <p>Protection level: signature|role + @hide + --> + <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" + android:protectionLevel="signature|role" /> + + <!-- @SystemApi Allows an application to read the system grammatical gender. + @FlaggedApi("android.app.system_terms_of_address_enabled") + <p>Protection level: signature|privileged|appop + @hide + --> + <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" + android:protectionLevel="signature|privileged|appop"/> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml index f3acab063bbf..fe12f6ee7836 100644 --- a/core/res/res/drawable-nodpi/platlogo.xml +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2021 The Android Open Source Project +Copyright (C) 2024 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. @@ -20,179 +20,103 @@ Copyright (C) 2021 The Android Open Source Project android:viewportWidth="512" android:viewportHeight="512"> <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"> + android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="0"> <aapt:attr name="android:fillColor"> <gradient - android:startX="256" - android:startY="21.81" - android:endX="256" - android:endY="350.42" + android:startX="56.22" + android:startY="256" + android:endX="456.22" + android:endY="256" android:type="linear"> <item android:offset="0" android:color="#FF073042"/> <item android:offset="1" android:color="#FF073042"/> </gradient> </aapt:attr> </path> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z" - android:fillColor="#3ddc84"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M171.92,216.82h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,275.73h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M369.04,337.63h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M285.93,252.22h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M318.96,218.84h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M294.05,288.55h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M330.82,273.31h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M188.8,298.95h4.04v4.04h-4.04z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M220.14,238.94h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M272.1,318.9h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M293.34,349.25h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M161.05,254.24h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M378.92,192h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <group> - <clip-path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/> - <path - android:pathData="M137.87,323.7h8.08v8.08h-8.08z" - android:fillColor="#fff"/> - </group> - <path - android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0" - android:strokeWidth="56.561" - android:fillColor="#00000000" - android:strokeColor="#f86734"/> <path - android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z" + android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z" + android:strokeWidth="0" android:fillColor="#3ddc84"/> <path - android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z" + android:pathData="M159.03,176.91h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M188.8,275.73h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M373.41,158.93h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M112.1,129.34h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M285.93,252.22h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M318.96,218.84h4.04v4.04h-4.04z" + android:strokeWidth="0" + android:fillColor="#fff"/> + <path + android:pathData="M294.05,288.55h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z" + android:pathData="M330.82,263.31h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z" + android:pathData="M188.8,298.95h4.04v4.04h-4.04z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z" + android:pathData="M224.18,216.82h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z" + android:pathData="M272.1,318.9h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z" + android:pathData="M293.34,339.25h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z" + android:pathData="M165.09,236.28h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z" + android:pathData="M378.92,192h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> <path - android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z" + android:pathData="M204.28,314.86h8.08v8.08h-8.08z" + android:strokeWidth="0" android:fillColor="#fff"/> + <path + android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z" + android:strokeWidth="0" + android:fillColor="#3ddc84"/> + <path + android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z" + android:strokeWidth="55" + android:fillColor="#00000000" + android:strokeColor="#f86733"/> </vector> - diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5cfb1a3e44ba..2eb28eb555e8 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4372,6 +4372,14 @@ <attr name="name" /> </declare-styleable> + <!-- Specify one or more <code>polling-loop-filter</code> elements inside a + <code>host-apdu-service</code> to indicate polling loop frames that + your service can handle. --> + <declare-styleable name="PollingLoopFilter"> + <!-- The polling loop frame. This attribute is mandatory. --> + <attr name="name" /> + </declare-styleable> + <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that describes an {@link android.nfc.cardemulation.HostNfcFService} service, which is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA} diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 53464547c272..d0216b308a4c 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -192,8 +192,13 @@ <!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC of the satellite PLMN with the format "mccmnc". --> - <string name="config_satellite_sim_identifier" translatable="false"></string> - <java-symbol type="string" name="config_satellite_sim_identifier" /> + <string name="config_satellite_sim_plmn_identifier" translatable="false"></string> + <java-symbol type="string" name="config_satellite_sim_plmn_identifier" /> + + <!-- The identifier for the satellite's SIM profile. The identifier is the service provider name + (spn) from the profile metadata. --> + <string name="config_satellite_sim_spn_identifier" translatable="false"></string> + <java-symbol type="string" name="config_satellite_sim_spn_identifier" /> <!-- The app to which the emergency call will be handed over for OEM-enabled satellite messaging. The format of the config string is "package_name;class_name". --> diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING new file mode 100644 index 000000000000..b085a27b25b8 --- /dev/null +++ b/core/tests/BroadcastRadioTests/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "BroadcastRadioTests" + } + ] +} diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt index 1617eda6a77c..e32a57b1aefe 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -48,7 +48,7 @@ class FontScaleConverterFactoryTest { @get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - private lateinit var defaultLookupTables: SparseArray<FontScaleConverter> + private var defaultLookupTables: SparseArray<FontScaleConverter>? = null @Before fun setup() { @@ -58,7 +58,9 @@ class FontScaleConverterFactoryTest { @After fun teardown() { // Restore the default tables (since some tests will have added extras to the cache) - FontScaleConverterFactory.sLookupTables = defaultLookupTables + if (defaultLookupTables != null) { + FontScaleConverterFactory.sLookupTables = defaultLookupTables!! + } } @Test diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt new file mode 100644 index 000000000000..27869bbf5392 --- /dev/null +++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text + +import android.graphics.Paint +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE +const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE +const val MIDDLE_OF_LINE = 0 +const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TextLineLetterSpacingTest { + + @Rule + @JvmField + val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION) + @Test + fun calculateRunFlagTest() { + // Only one Bidi run + assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(WHOLE_LINE) + + // Two BiDi Runs. + // If the layout is LTR, the first run is the left most run. + assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(LEFT_EDGE) + // If the layout is LTR, the last run is the right most run. + assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the first run is the right most run. + assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the last run is the left most run. + assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(LEFT_EDGE) + + // Three BiDi Runs. + // If the layout is LTR, the first run is the left most run. + assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(LEFT_EDGE) + // Regardless of the context direction, the middle run must not have any flags. + assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(MIDDLE_OF_LINE) + // If the layout is LTR, the last run is the right most run. + assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT)) + .isEqualTo(RIGHT_EDGE) + // If the layout is RTL, the first run is the right most run. + assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(RIGHT_EDGE) + // Regardless of the context direction, the middle run must not have any flags. + assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(MIDDLE_OF_LINE) + // If the layout is RTL, the last run is the left most run. + assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT)) + .isEqualTo(LEFT_EDGE) + } + + @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION) + @Test + fun resolveRunFlagForSubSequenceTest() { + val runStart = 5 + val runEnd = 15 + // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag + // should be returned. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(LEFT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(LEFT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(RIGHT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(RIGHT_EDGE) + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(WHOLE_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(LEFT_EDGE) + // Left edge of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(RIGHT_EDGE) + // Whole line of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(LEFT_EDGE) + // Whole line of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(RIGHT_EDGE) + // Middle of LTR text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from run start offset but not cover the run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Left edge of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(LEFT_EDGE) + // Right edge of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(RIGHT_EDGE) + // Right edge of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(RIGHT_EDGE) + // Whole line of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(LEFT_EDGE) + // Middle of LTR text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from middle of run start offset until end of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd)) + .isEqualTo(MIDDLE_OF_LINE) + + + + // Left edge of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Left edge of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Right edge of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Whole line of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of LTR text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + // Middle of RTL text, span start from middle of run. + assertThat(TextLine.resolveRunFlagForSubSequence( + MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1)) + .isEqualTo(MIDDLE_OF_LINE) + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index cf3eb12498ca..cfbda84f24e1 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -575,8 +576,13 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), + FRAME_RATE_CATEGORY_HIGH_HINT); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT); + assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java index 0742052cce53..ec4c563e469e 100644 --- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -18,39 +18,67 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; -import android.util.Xml; - import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.nio.file.Files; @RunWith(AndroidJUnit4.class) @SmallTest public class MonotonicClockTest { private final MockClock mClock = new MockClock(); + private File mFile; + + @Before + public void setup() throws IOException { + File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile(); + mFile = new File(systemDir, "test_monotonic_clock.xml"); + if (mFile.exists()) { + assertThat(mFile.delete()).isTrue(); + } + } @Test public void persistence() throws IOException { - MonotonicClock monotonicClock = new MonotonicClock(1000, mClock); + MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock); mClock.realtime = 234; assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - monotonicClock.writeXml(out, Xml.newBinarySerializer()); + monotonicClock.write(); mClock.realtime = 42; - MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - newMonotonicClock.readXml(in, Xml.newBinaryPullParser()); + MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock); mClock.realtime = 2000; assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000); } + + @Test + public void constructor() { + MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock); + mClock.realtime = 234; + + assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + } + + @Test + public void corruptedFile() throws IOException { + // Create an invalid binary XML file to cause IOException: "Unexpected magic number" + try (FileWriter w = new FileWriter(mFile)) { + w.write("garbage"); + } + + MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock); + mClock.realtime = 234; + + assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + } } diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml index d32cbecb16ec..2e521e30c673 100644 --- a/data/etc/com.android.documentsui.xml +++ b/data/etc/com.android.documentsui.xml @@ -23,5 +23,7 @@ <permission name="android.permission.MODIFY_QUIET_MODE"/> <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> <permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/> + <!-- Permissions required for reading device configs --> + <permission name="android.permission.READ_DEVICE_CONFIG" /> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index a1ea2b8ce032..4be75f83422e 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -426,6 +426,7 @@ applications that come with the platform <!-- Permissions required for CTS test - android.server.biometrics --> <permission name="android.permission.USE_BIOMETRIC" /> <permission name="android.permission.TEST_BIOMETRIC" /> + <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> <!-- Permissions required for CTS test - CtsContactsProviderTestCases --> <permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" /> <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases --> @@ -541,6 +542,8 @@ applications that come with the platform <permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/> <!-- Permission required for CTS test NotificationManagerZenTest --> <permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp index 3dd9ba9db1d9..471acaa0b1b6 100644 --- a/data/fonts/Android.bp +++ b/data/fonts/Android.bp @@ -48,12 +48,35 @@ prebuilt_font { // Copies the font configuration file into system/etc for the product as fonts.xml. // Additional fonts should be installed to /product/fonts/ alongside a corresponding // fonts_customiztion.xml in /product/etc/ -prebuilt_etc { + +soong_config_bool_variable { + name: "use_var_font", +} + +soong_config_module_type { + name: "prebuilt_fonts_xml", + module_type: "prebuilt_etc", + config_namespace: "noto_sans_cjk_config", + bool_variables: ["use_var_font"], + properties: ["src"], +} + +prebuilt_fonts_xml { name: "fonts.xml", src: "fonts.xml", + soong_config_variables: { + use_var_font: { + src: "fonts_cjkvf.xml", + }, + }, } -prebuilt_etc { +prebuilt_fonts_xml { name: "font_fallback.xml", src: "font_fallback.xml", + soong_config_variables: { + use_var_font: { + src: "font_fallback_cjkvf.xml", + }, + }, } diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml new file mode 100644 index 000000000000..70474bae11e9 --- /dev/null +++ b/data/fonts/font_fallback_cjkvf.xml @@ -0,0 +1,1015 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + In this file, all fonts without names are added to the default list. + Fonts are chosen based on a match: full BCP-47 language tag including + script, then just language, and finally order (the first font containing + the glyph). + + Order of appearance is also the tiebreaker for weight matching. This is + the reason why the 900 weights of Roboto precede the 700 weights - we + prefer the former when an 800 weight is requested. Since bold spans + effectively add 300 to the weight, this ensures that 900 is the bold + paired with the 500 weight, ensuring adequate contrast. + + TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required +--> +<familyset version="23"> + <!-- first font is default --> + <family name="sans-serif" varFamilyType="2"> + <font>Roboto-Regular.ttf + <axis tag="wdth" stylevalue="100" /> + </font> + </family> + + + <!-- Note that aliases must come after the fonts they reference. --> + <alias name="sans-serif-thin" to="sans-serif" weight="100" /> + <alias name="sans-serif-light" to="sans-serif" weight="300" /> + <alias name="sans-serif-medium" to="sans-serif" weight="500" /> + <alias name="sans-serif-black" to="sans-serif" weight="900" /> + <alias name="arial" to="sans-serif" /> + <alias name="helvetica" to="sans-serif" /> + <alias name="tahoma" to="sans-serif" /> + <alias name="verdana" to="sans-serif" /> + + <family name="sans-serif-condensed" varFamilyType="2"> + <font>Roboto-Regular.ttf + <axis tag="wdth" stylevalue="75" /> + </font> + </family> + <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" /> + <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" /> + + <family name="serif"> + <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font> + <font weight="700" style="normal">NotoSerif-Bold.ttf</font> + <font weight="400" style="italic">NotoSerif-Italic.ttf</font> + <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font> + </family> + <alias name="serif-bold" to="serif" weight="700" /> + <alias name="times" to="serif" /> + <alias name="times new roman" to="serif" /> + <alias name="palatino" to="serif" /> + <alias name="georgia" to="serif" /> + <alias name="baskerville" to="serif" /> + <alias name="goudy" to="serif" /> + <alias name="fantasy" to="serif" /> + <alias name="ITC Stone Serif" to="serif" /> + + <family name="monospace"> + <font weight="400" style="normal">DroidSansMono.ttf</font> + </family> + <alias name="sans-serif-monospace" to="monospace" /> + <alias name="monaco" to="monospace" /> + + <family name="serif-monospace"> + <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font> + </family> + <alias name="courier" to="serif-monospace" /> + <alias name="courier new" to="serif-monospace" /> + + <family name="casual"> + <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font> + </family> + + <family name="cursive" varFamilyType="1"> + <font>DancingScript-Regular.ttf</font> + </family> + + <family name="sans-serif-smallcaps"> + <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> + </family> + + <family name="source-sans-pro"> + <font weight="400" style="normal">SourceSansPro-Regular.ttf</font> + <font weight="400" style="italic">SourceSansPro-Italic.ttf</font> + <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font> + <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font> + <font weight="700" style="normal">SourceSansPro-Bold.ttf</font> + <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font> + </family> + <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/> + + <family name="roboto-flex" varFamilyType="2"> + <font>RobotoFlex-Regular.ttf + <axis tag="wdth" stylevalue="100" /> + </font> + </family> + + <!-- fallback fonts --> + <family lang="und-Arab" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoNaskhArabic"> + NotoNaskhArabic-Regular.ttf + </font> + <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font> + </family> + <family lang="und-Arab" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI"> + NotoNaskhArabicUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> + </family> + <family lang="und-Ethi" varFamilyType="1"> + <font postScriptName="NotoSansEthiopic-Regular"> + NotoSansEthiopic-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular"> + NotoSerifEthiopic-VF.ttf + </font> + </family> + <family lang="und-Hebr"> + <font weight="400" style="normal" postScriptName="NotoSansHebrew"> + NotoSansHebrew-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font> + </family> + <family lang="und-Thai" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThai-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif"> + NotoSerifThai-Regular.ttf + </font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font> + </family> + <family lang="und-Thai" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansThaiUI"> + NotoSansThaiUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font> + </family> + <family lang="und-Armn" varFamilyType="1"> + <font postScriptName="NotoSansArmenian-Regular"> + NotoSansArmenian-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular"> + NotoSerifArmenian-VF.ttf + </font> + </family> + <family lang="und-Geor,und-Geok" varFamilyType="1"> + <font postScriptName="NotoSansGeorgian-Regular"> + NotoSansGeorgian-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular"> + NotoSerifGeorgian-VF.ttf + </font> + </family> + <family lang="und-Deva" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansDevanagari-Regular"> + NotoSansDevanagari-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular"> + NotoSerifDevanagari-VF.ttf + </font> + </family> + <family lang="und-Deva" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansDevanagariUI-Regular"> + NotoSansDevanagariUI-VF.ttf + </font> + </family> + + <!-- All scripts of India should come after Devanagari, due to shared + danda characters. + --> + <family lang="und-Gujr" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansGujarati"> + NotoSansGujarati-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Gujr" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI"> + NotoSansGujaratiUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font> + </family> + <family lang="und-Guru" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansGurmukhi-Regular"> + NotoSansGurmukhi-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular"> + NotoSerifGurmukhi-VF.ttf + </font> + </family> + <family lang="und-Guru" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansGurmukhiUI-Regular"> + NotoSansGurmukhiUI-VF.ttf + </font> + </family> + <family lang="und-Taml" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansTamil-Regular"> + NotoSansTamil-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular"> + NotoSerifTamil-VF.ttf + </font> + </family> + <family lang="und-Taml" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansTamilUI-Regular"> + NotoSansTamilUI-VF.ttf + </font> + </family> + <family lang="und-Mlym" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansMalayalam-Regular"> + NotoSansMalayalam-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular"> + NotoSerifMalayalam-VF.ttf + </font> + </family> + <family lang="und-Mlym" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansMalayalamUI-Regular"> + NotoSansMalayalamUI-VF.ttf + </font> + </family> + <family lang="und-Beng" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansBengali-Regular"> + NotoSansBengali-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular"> + NotoSerifBengali-VF.ttf + </font> + </family> + <family lang="und-Beng" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansBengaliUI-Regular"> + NotoSansBengaliUI-VF.ttf + </font> + </family> + <family lang="und-Telu" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansTelugu-Regular"> + NotoSansTelugu-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular"> + NotoSerifTelugu-VF.ttf + </font> + </family> + <family lang="und-Telu" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansTeluguUI-Regular"> + NotoSansTeluguUI-VF.ttf + </font> + </family> + <family lang="und-Knda" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansKannada-Regular"> + NotoSansKannada-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular"> + NotoSerifKannada-VF.ttf + </font> + </family> + <family lang="und-Knda" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansKannadaUI-Regular"> + NotoSansKannadaUI-VF.ttf + </font> + </family> + <family lang="und-Orya" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font> + </family> + <family lang="und-Orya" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansOriyaUI"> + NotoSansOriyaUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font> + </family> + <family lang="und-Sinh" variant="elegant" varFamilyType="1"> + <font postScriptName="NotoSansSinhala-Regular"> + NotoSansSinhala-VF.ttf + </font> + <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular"> + NotoSerifSinhala-VF.ttf + </font> + </family> + <family lang="und-Sinh" variant="compact" varFamilyType="1"> + <font postScriptName="NotoSansSinhalaUI-Regular"> + NotoSansSinhalaUI-VF.ttf + </font> + </family> + <family lang="und-Khmr" variant="elegant"> + <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="26.0"/> + </font> + <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="39.0"/> + </font> + <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="58.0"/> + </font> + <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="90.0"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="108.0"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="128.0"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="151.0"/> + </font> + <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="169.0"/> + </font> + <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="190.0"/> + </font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font> + </family> + <family lang="und-Khmr" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansKhmerUI"> + NotoSansKhmerUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font> + </family> + <family lang="und-Laoo" variant="elegant"> + <font weight="400" style="normal">NotoSansLao-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansLao-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif"> + NotoSerifLao-Regular.ttf + </font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font> + </family> + <family lang="und-Laoo" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> + </family> + <family lang="und-Mymr" variant="elegant"> + <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font> + <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font> + <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font> + </family> + <family lang="und-Mymr" variant="compact"> + <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font> + <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font> + <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font> + </family> + <family lang="und-Thaa"> + <font weight="400" style="normal" postScriptName="NotoSansThaana"> + NotoSansThaana-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font> + </family> + <family lang="und-Cham"> + <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> + </family> + <family lang="und-Ahom"> + <font weight="400" style="normal">NotoSansAhom-Regular.otf</font> + </family> + <family lang="und-Adlm" varFamilyType="1"> + <font postScriptName="NotoSansAdlam-Regular"> + NotoSansAdlam-VF.ttf + </font> + </family> + <family lang="und-Avst"> + <font weight="400" style="normal" postScriptName="NotoSansAvestan"> + NotoSansAvestan-Regular.ttf + </font> + </family> + <family lang="und-Bali"> + <font weight="400" style="normal" postScriptName="NotoSansBalinese"> + NotoSansBalinese-Regular.ttf + </font> + </family> + <family lang="und-Bamu"> + <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf + </font> + </family> + <family lang="und-Batk"> + <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf + </font> + </family> + <family lang="und-Brah"> + <font weight="400" style="normal" postScriptName="NotoSansBrahmi"> + NotoSansBrahmi-Regular.ttf + </font> + </family> + <family lang="und-Bugi"> + <font weight="400" style="normal" postScriptName="NotoSansBuginese"> + NotoSansBuginese-Regular.ttf + </font> + </family> + <family lang="und-Buhd"> + <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf + </font> + </family> + <family lang="und-Cans"> + <font weight="400" style="normal"> + NotoSansCanadianAboriginal-Regular.ttf + </font> + </family> + <family lang="und-Cari"> + <font weight="400" style="normal" postScriptName="NotoSansCarian"> + NotoSansCarian-Regular.ttf + </font> + </family> + <family lang="und-Cakm"> + <font weight="400" style="normal">NotoSansChakma-Regular.otf</font> + </family> + <family lang="und-Cher"> + <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> + </family> + <family lang="und-Copt"> + <font weight="400" style="normal" postScriptName="NotoSansCoptic"> + NotoSansCoptic-Regular.ttf + </font> + </family> + <family lang="und-Xsux"> + <font weight="400" style="normal" postScriptName="NotoSansCuneiform"> + NotoSansCuneiform-Regular.ttf + </font> + </family> + <family lang="und-Cprt"> + <font weight="400" style="normal" postScriptName="NotoSansCypriot"> + NotoSansCypriot-Regular.ttf + </font> + </family> + <family lang="und-Dsrt"> + <font weight="400" style="normal" postScriptName="NotoSansDeseret"> + NotoSansDeseret-Regular.ttf + </font> + </family> + <family lang="und-Egyp"> + <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs"> + NotoSansEgyptianHieroglyphs-Regular.ttf + </font> + </family> + <family lang="und-Elba"> + <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font> + </family> + <family lang="und-Glag"> + <font weight="400" style="normal" postScriptName="NotoSansGlagolitic"> + NotoSansGlagolitic-Regular.ttf + </font> + </family> + <family lang="und-Goth"> + <font weight="400" style="normal" postScriptName="NotoSansGothic"> + NotoSansGothic-Regular.ttf + </font> + </family> + <family lang="und-Hano"> + <font weight="400" style="normal" postScriptName="NotoSansHanunoo"> + NotoSansHanunoo-Regular.ttf + </font> + </family> + <family lang="und-Armi"> + <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic"> + NotoSansImperialAramaic-Regular.ttf + </font> + </family> + <family lang="und-Phli"> + <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi"> + NotoSansInscriptionalPahlavi-Regular.ttf + </font> + </family> + <family lang="und-Prti"> + <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian"> + NotoSansInscriptionalParthian-Regular.ttf + </font> + </family> + <family lang="und-Java"> + <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font> + </family> + <family lang="und-Kthi"> + <font weight="400" style="normal" postScriptName="NotoSansKaithi"> + NotoSansKaithi-Regular.ttf + </font> + </family> + <family lang="und-Kali"> + <font weight="400" style="normal" postScriptName="NotoSansKayahLi"> + NotoSansKayahLi-Regular.ttf + </font> + </family> + <family lang="und-Khar"> + <font weight="400" style="normal" postScriptName="NotoSansKharoshthi"> + NotoSansKharoshthi-Regular.ttf + </font> + </family> + <family lang="und-Lepc"> + <font weight="400" style="normal" postScriptName="NotoSansLepcha"> + NotoSansLepcha-Regular.ttf + </font> + </family> + <family lang="und-Limb"> + <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf + </font> + </family> + <family lang="und-Linb"> + <font weight="400" style="normal" postScriptName="NotoSansLinearB"> + NotoSansLinearB-Regular.ttf + </font> + </family> + <family lang="und-Lisu"> + <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf + </font> + </family> + <family lang="und-Lyci"> + <font weight="400" style="normal" postScriptName="NotoSansLycian"> + NotoSansLycian-Regular.ttf + </font> + </family> + <family lang="und-Lydi"> + <font weight="400" style="normal" postScriptName="NotoSansLydian"> + NotoSansLydian-Regular.ttf + </font> + </family> + <family lang="und-Mand"> + <font weight="400" style="normal" postScriptName="NotoSansMandaic"> + NotoSansMandaic-Regular.ttf + </font> + </family> + <family lang="und-Mtei"> + <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek"> + NotoSansMeeteiMayek-Regular.ttf + </font> + </family> + <family lang="und-Talu"> + <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue"> + NotoSansNewTaiLue-Regular.ttf + </font> + </family> + <family lang="und-Nkoo"> + <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf + </font> + </family> + <family lang="und-Ogam"> + <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf + </font> + </family> + <family lang="und-Olck"> + <font weight="400" style="normal" postScriptName="NotoSansOlChiki"> + NotoSansOlChiki-Regular.ttf + </font> + </family> + <family lang="und-Ital"> + <font weight="400" style="normal" postScriptName="NotoSansOldItalic"> + NotoSansOldItalic-Regular.ttf + </font> + </family> + <family lang="und-Xpeo"> + <font weight="400" style="normal" postScriptName="NotoSansOldPersian"> + NotoSansOldPersian-Regular.ttf + </font> + </family> + <family lang="und-Sarb"> + <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian"> + NotoSansOldSouthArabian-Regular.ttf + </font> + </family> + <family lang="und-Orkh"> + <font weight="400" style="normal" postScriptName="NotoSansOldTurkic"> + NotoSansOldTurkic-Regular.ttf + </font> + </family> + <family lang="und-Osge"> + <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font> + </family> + <family lang="und-Osma"> + <font weight="400" style="normal" postScriptName="NotoSansOsmanya"> + NotoSansOsmanya-Regular.ttf + </font> + </family> + <family lang="und-Phnx"> + <font weight="400" style="normal" postScriptName="NotoSansPhoenician"> + NotoSansPhoenician-Regular.ttf + </font> + </family> + <family lang="und-Rjng"> + <font weight="400" style="normal" postScriptName="NotoSansRejang"> + NotoSansRejang-Regular.ttf + </font> + </family> + <family lang="und-Runr"> + <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf + </font> + </family> + <family lang="und-Samr"> + <font weight="400" style="normal" postScriptName="NotoSansSamaritan"> + NotoSansSamaritan-Regular.ttf + </font> + </family> + <family lang="und-Saur"> + <font weight="400" style="normal" postScriptName="NotoSansSaurashtra"> + NotoSansSaurashtra-Regular.ttf + </font> + </family> + <family lang="und-Shaw"> + <font weight="400" style="normal" postScriptName="NotoSansShavian"> + NotoSansShavian-Regular.ttf + </font> + </family> + <family lang="und-Sund"> + <font weight="400" style="normal" postScriptName="NotoSansSundanese"> + NotoSansSundanese-Regular.ttf + </font> + </family> + <family lang="und-Sylo"> + <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri"> + NotoSansSylotiNagri-Regular.ttf + </font> + </family> + <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. --> + <family lang="und-Syre"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela"> + NotoSansSyriacEstrangela-Regular.ttf + </font> + </family> + <family lang="und-Syrn"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern"> + NotoSansSyriacEastern-Regular.ttf + </font> + </family> + <family lang="und-Syrj"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern"> + NotoSansSyriacWestern-Regular.ttf + </font> + </family> + <family lang="und-Tglg"> + <font weight="400" style="normal" postScriptName="NotoSansTagalog"> + NotoSansTagalog-Regular.ttf + </font> + </family> + <family lang="und-Tagb"> + <font weight="400" style="normal" postScriptName="NotoSansTagbanwa"> + NotoSansTagbanwa-Regular.ttf + </font> + </family> + <family lang="und-Lana"> + <font weight="400" style="normal" postScriptName="NotoSansTaiTham"> + NotoSansTaiTham-Regular.ttf + </font> + </family> + <family lang="und-Tavt"> + <font weight="400" style="normal" postScriptName="NotoSansTaiViet"> + NotoSansTaiViet-Regular.ttf + </font> + </family> + <family lang="und-Tibt" varFamilyType="1"> + <font postScriptName="NotoSerifTibetan-Regular"> + NotoSerifTibetan-VF.ttf + </font> + </family> + <family lang="und-Tfng"> + <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font> + </family> + <family lang="und-Ugar"> + <font weight="400" style="normal" postScriptName="NotoSansUgaritic"> + NotoSansUgaritic-Regular.ttf + </font> + </family> + <family lang="und-Vaii"> + <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf + </font> + </family> + <family> + <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> + </family> + <family lang="zh-Hans"> + <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="2" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="zh-Hant,zh-Bopo"> + <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="3" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="ja"> + <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="0" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="ko"> + <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="1" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">NotoColorEmoji.ttf</font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font> + </family> + <family lang="und-Zsym"> + <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font> + </family> + <!-- + Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't + override the East Asian punctuation for Chinese. + --> + <family lang="und-Tale"> + <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf + </font> + </family> + <family lang="und-Yiii"> + <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font> + </family> + <family lang="und-Mong"> + <font weight="400" style="normal" postScriptName="NotoSansMongolian"> + NotoSansMongolian-Regular.ttf + </font> + </family> + <family lang="und-Phag"> + <font weight="400" style="normal" postScriptName="NotoSansPhagsPa"> + NotoSansPhagsPa-Regular.ttf + </font> + </family> + <family lang="und-Hluw"> + <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font> + </family> + <family lang="und-Bass"> + <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font> + </family> + <family lang="und-Bhks"> + <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font> + </family> + <family lang="und-Hatr"> + <font weight="400" style="normal">NotoSansHatran-Regular.otf</font> + </family> + <family lang="und-Lina"> + <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font> + </family> + <family lang="und-Mani"> + <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font> + </family> + <family lang="und-Marc"> + <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font> + </family> + <family lang="und-Merc"> + <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font> + </family> + <family lang="und-Plrd"> + <font weight="400" style="normal">NotoSansMiao-Regular.otf</font> + </family> + <family lang="und-Mroo"> + <font weight="400" style="normal">NotoSansMro-Regular.otf</font> + </family> + <family lang="und-Mult"> + <font weight="400" style="normal">NotoSansMultani-Regular.otf</font> + </family> + <family lang="und-Nbat"> + <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font> + </family> + <family lang="und-Newa"> + <font weight="400" style="normal">NotoSansNewa-Regular.otf</font> + </family> + <family lang="und-Narb"> + <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font> + </family> + <family lang="und-Perm"> + <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font> + </family> + <family lang="und-Hmng"> + <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font> + </family> + <family lang="und-Palm"> + <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font> + </family> + <family lang="und-Pauc"> + <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font> + </family> + <family lang="und-Shrd"> + <font weight="400" style="normal">NotoSansSharada-Regular.otf</font> + </family> + <family lang="und-Sora"> + <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font> + </family> + <family lang="und-Gong"> + <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font> + </family> + <family lang="und-Rohg"> + <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font> + </family> + <family lang="und-Khoj"> + <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font> + </family> + <family lang="und-Gonm"> + <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font> + </family> + <family lang="und-Wcho"> + <font weight="400" style="normal">NotoSansWancho-Regular.otf</font> + </family> + <family lang="und-Wara"> + <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font> + </family> + <family lang="und-Gran"> + <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font> + </family> + <family lang="und-Modi"> + <font weight="400" style="normal">NotoSansModi-Regular.ttf</font> + </family> + <family lang="und-Dogr"> + <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font> + </family> + <family lang="und-Medf" varFamilyType="1"> + <font postScriptName="NotoSansMedefaidrin-Regular"> + NotoSansMedefaidrin-VF.ttf + </font> + </family> + <family lang="und-Soyo" varFamilyType="1"> + <font postScriptName="NotoSansSoyombo-Regular"> + NotoSansSoyombo-VF.ttf + </font> + </family> + <family lang="und-Takr" varFamilyType="1"> + <font postScriptName="NotoSansTakri-Regular"> + NotoSansTakri-VF.ttf + </font> + </family> + <family lang="und-Hmnp" varFamilyType="1"> + <font postScriptName="NotoSerifHmongNyiakeng-Regular"> + NotoSerifNyiakengPuachueHmong-VF.ttf + </font> + </family> + <family lang="und-Yezi" varFamilyType="1"> + <font postScriptName="NotoSerifYezidi-Regular"> + NotoSerifYezidi-VF.ttf + </font> + </family> +</familyset> diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml new file mode 100644 index 000000000000..8d91eddd807d --- /dev/null +++ b/data/fonts/fonts_cjkvf.xml @@ -0,0 +1,1785 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + DEPRECATED: This XML file is no longer a source of the font files installed + in the system. + + For the device vendors: please add your font configurations to the + platform/frameworks/base/data/font_fallback.xml and also add it to this XML + file as much as possible for apps that reads this XML file. + + For the application developers: please stop reading this XML file and use + android.graphics.fonts.SystemFonts#getAvailableFonts Java API or + ASystemFontIterator_open NDK API for getting list of system installed + font files. + + WARNING: Parsing of this file by third-party apps is not supported. The + file, and the font files it refers to, will be renamed and/or moved out + from their respective location in the next Android release, and/or the + format or syntax of the file may change significantly. If you parse this + file for information about system fonts, do it at your own risk. Your + application will almost certainly break with the next major Android + release. + + In this file, all fonts without names are added to the default list. + Fonts are chosen based on a match: full BCP-47 language tag including + script, then just language, and finally order (the first font containing + the glyph). + + Order of appearance is also the tiebreaker for weight matching. This is + the reason why the 900 weights of Roboto precede the 700 weights - we + prefer the former when an 800 weight is requested. Since bold spans + effectively add 300 to the weight, this ensures that 900 is the bold + paired with the 500 weight, ensuring adequate contrast. + + TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required +--> +<familyset version="23"> + <!-- first font is default --> + <family name="sans-serif"> + <font weight="100" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="900" /> + </font> + <font weight="100" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="900" /> + </font> + </family> + + + <!-- Note that aliases must come after the fonts they reference. --> + <alias name="sans-serif-thin" to="sans-serif" weight="100" /> + <alias name="sans-serif-light" to="sans-serif" weight="300" /> + <alias name="sans-serif-medium" to="sans-serif" weight="500" /> + <alias name="sans-serif-black" to="sans-serif" weight="900" /> + <alias name="arial" to="sans-serif" /> + <alias name="helvetica" to="sans-serif" /> + <alias name="tahoma" to="sans-serif" /> + <alias name="verdana" to="sans-serif" /> + + <family name="sans-serif-condensed"> + <font weight="100" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="normal">Roboto-Regular.ttf + <axis tag="ital" stylevalue="0" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="900" /> + </font> + <font weight="100" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="italic">Roboto-Regular.ttf + <axis tag="ital" stylevalue="1" /> + <axis tag="wdth" stylevalue="75" /> + <axis tag="wght" stylevalue="900" /> + </font> + </family> + <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" /> + <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" /> + + <family name="serif"> + <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font> + <font weight="700" style="normal">NotoSerif-Bold.ttf</font> + <font weight="400" style="italic">NotoSerif-Italic.ttf</font> + <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font> + </family> + <alias name="serif-bold" to="serif" weight="700" /> + <alias name="times" to="serif" /> + <alias name="times new roman" to="serif" /> + <alias name="palatino" to="serif" /> + <alias name="georgia" to="serif" /> + <alias name="baskerville" to="serif" /> + <alias name="goudy" to="serif" /> + <alias name="fantasy" to="serif" /> + <alias name="ITC Stone Serif" to="serif" /> + + <family name="monospace"> + <font weight="400" style="normal">DroidSansMono.ttf</font> + </family> + <alias name="sans-serif-monospace" to="monospace" /> + <alias name="monaco" to="monospace" /> + + <family name="serif-monospace"> + <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font> + </family> + <alias name="courier" to="serif-monospace" /> + <alias name="courier new" to="serif-monospace" /> + + <family name="casual"> + <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font> + </family> + + <family name="cursive"> + <font weight="400" style="normal">DancingScript-Regular.ttf + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="700" style="normal">DancingScript-Regular.ttf + <axis tag="wght" stylevalue="700" /> + </font> + </family> + + <family name="sans-serif-smallcaps"> + <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> + </family> + + <family name="source-sans-pro"> + <font weight="400" style="normal">SourceSansPro-Regular.ttf</font> + <font weight="400" style="italic">SourceSansPro-Italic.ttf</font> + <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font> + <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font> + <font weight="700" style="normal">SourceSansPro-Bold.ttf</font> + <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font> + </family> + <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/> + + <family name="roboto-flex"> + <font weight="100" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="normal">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="0" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="900" /> + </font> + <font weight="100" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="100" /> + </font> + <font weight="200" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="200" /> + </font> + <font weight="300" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="300" /> + </font> + <font weight="400" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="400" /> + </font> + <font weight="500" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="500" /> + </font> + <font weight="600" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="600" /> + </font> + <font weight="700" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="700" /> + </font> + <font weight="800" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="800" /> + </font> + <font weight="900" style="italic">RobotoFlex-Regular.ttf + <axis tag="slnt" stylevalue="-10" /> + <axis tag="wdth" stylevalue="100" /> + <axis tag="wght" stylevalue="900" /> + </font> + </family> + + <!-- fallback fonts --> + <family lang="und-Arab" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoNaskhArabic"> + NotoNaskhArabic-Regular.ttf + </font> + <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font> + </family> + <family lang="und-Arab" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI"> + NotoNaskhArabicUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> + </family> + <family lang="und-Ethi"> + <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular"> + NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular"> + NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular"> + NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular"> + NotoSansEthiopic-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Hebr"> + <font weight="400" style="normal" postScriptName="NotoSansHebrew"> + NotoSansHebrew-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font> + </family> + <family lang="und-Thai" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThai-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif"> + NotoSerifThai-Regular.ttf + </font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font> + </family> + <family lang="und-Thai" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansThaiUI"> + NotoSansThaiUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font> + </family> + <family lang="und-Armn"> + <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular"> + NotoSansArmenian-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular"> + NotoSansArmenian-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular"> + NotoSansArmenian-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular"> + NotoSansArmenian-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Geor,und-Geok"> + <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular"> + NotoSansGeorgian-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular"> + NotoSansGeorgian-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular"> + NotoSansGeorgian-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular"> + NotoSansGeorgian-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Deva" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular"> + NotoSansDevanagari-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular"> + NotoSansDevanagari-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular"> + NotoSansDevanagari-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular"> + NotoSansDevanagari-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Deva" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> + NotoSansDevanagariUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> + NotoSansDevanagariUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> + NotoSansDevanagariUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular"> + NotoSansDevanagariUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + + <!-- All scripts of India should come after Devanagari, due to shared + danda characters. + --> + <family lang="und-Gujr" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansGujarati"> + NotoSansGujarati-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Gujr" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI"> + NotoSansGujaratiUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font> + </family> + <family lang="und-Guru" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular"> + NotoSansGurmukhi-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular"> + NotoSansGurmukhi-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular"> + NotoSansGurmukhi-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular"> + NotoSansGurmukhi-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Guru" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> + NotoSansGurmukhiUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> + NotoSansGurmukhiUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> + NotoSansGurmukhiUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular"> + NotoSansGurmukhiUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Taml" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular"> + NotoSansTamil-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular"> + NotoSansTamil-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular"> + NotoSansTamil-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular"> + NotoSansTamil-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Taml" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular"> + NotoSansTamilUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular"> + NotoSansTamilUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular"> + NotoSansTamilUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular"> + NotoSansTamilUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Mlym" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular"> + NotoSansMalayalam-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular"> + NotoSansMalayalam-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular"> + NotoSansMalayalam-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular"> + NotoSansMalayalam-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Mlym" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> + NotoSansMalayalamUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> + NotoSansMalayalamUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> + NotoSansMalayalamUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular"> + NotoSansMalayalamUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Beng" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular"> + NotoSansBengali-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular"> + NotoSansBengali-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular"> + NotoSansBengali-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular"> + NotoSansBengali-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Beng" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular"> + NotoSansBengaliUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular"> + NotoSansBengaliUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular"> + NotoSansBengaliUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular"> + NotoSansBengaliUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Telu" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular"> + NotoSansTelugu-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular"> + NotoSansTelugu-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular"> + NotoSansTelugu-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular"> + NotoSansTelugu-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Telu" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular"> + NotoSansTeluguUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular"> + NotoSansTeluguUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular"> + NotoSansTeluguUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular"> + NotoSansTeluguUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Knda" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular"> + NotoSansKannada-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular"> + NotoSansKannada-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular"> + NotoSansKannada-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular"> + NotoSansKannada-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Knda" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular"> + NotoSansKannadaUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular"> + NotoSansKannadaUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular"> + NotoSansKannadaUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular"> + NotoSansKannadaUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Orya" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font> + </family> + <family lang="und-Orya" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansOriyaUI"> + NotoSansOriyaUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font> + </family> + <family lang="und-Sinh" variant="elegant"> + <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular"> + NotoSansSinhala-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular"> + NotoSansSinhala-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular"> + NotoSansSinhala-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular"> + NotoSansSinhala-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="400" style="normal" fallbackFor="serif" + postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" fallbackFor="serif" + postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" fallbackFor="serif" + postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" fallbackFor="serif" + postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Sinh" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> + NotoSansSinhalaUI-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> + NotoSansSinhalaUI-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> + NotoSansSinhalaUI-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular"> + NotoSansSinhalaUI-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Khmr" variant="elegant"> + <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="26.0"/> + </font> + <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="39.0"/> + </font> + <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="58.0"/> + </font> + <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="90.0"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="108.0"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="128.0"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="151.0"/> + </font> + <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="169.0"/> + </font> + <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular"> + NotoSansKhmer-VF.ttf + <axis tag="wdth" stylevalue="100.0"/> + <axis tag="wght" stylevalue="190.0"/> + </font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font> + </family> + <family lang="und-Khmr" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansKhmerUI"> + NotoSansKhmerUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font> + </family> + <family lang="und-Laoo" variant="elegant"> + <font weight="400" style="normal">NotoSansLao-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansLao-Bold.ttf</font> + <font weight="400" style="normal" fallbackFor="serif"> + NotoSerifLao-Regular.ttf + </font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font> + </family> + <family lang="und-Laoo" variant="compact"> + <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> + </family> + <family lang="und-Mymr" variant="elegant"> + <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font> + <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font> + <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font> + <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font> + <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font> + </family> + <family lang="und-Mymr" variant="compact"> + <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font> + <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font> + <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font> + </family> + <family lang="und-Thaa"> + <font weight="400" style="normal" postScriptName="NotoSansThaana"> + NotoSansThaana-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font> + </family> + <family lang="und-Cham"> + <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf + </font> + <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> + </family> + <family lang="und-Ahom"> + <font weight="400" style="normal">NotoSansAhom-Regular.otf</font> + </family> + <family lang="und-Adlm"> + <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular"> + NotoSansAdlam-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular"> + NotoSansAdlam-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular"> + NotoSansAdlam-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular"> + NotoSansAdlam-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Avst"> + <font weight="400" style="normal" postScriptName="NotoSansAvestan"> + NotoSansAvestan-Regular.ttf + </font> + </family> + <family lang="und-Bali"> + <font weight="400" style="normal" postScriptName="NotoSansBalinese"> + NotoSansBalinese-Regular.ttf + </font> + </family> + <family lang="und-Bamu"> + <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf + </font> + </family> + <family lang="und-Batk"> + <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf + </font> + </family> + <family lang="und-Brah"> + <font weight="400" style="normal" postScriptName="NotoSansBrahmi"> + NotoSansBrahmi-Regular.ttf + </font> + </family> + <family lang="und-Bugi"> + <font weight="400" style="normal" postScriptName="NotoSansBuginese"> + NotoSansBuginese-Regular.ttf + </font> + </family> + <family lang="und-Buhd"> + <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf + </font> + </family> + <family lang="und-Cans"> + <font weight="400" style="normal"> + NotoSansCanadianAboriginal-Regular.ttf + </font> + </family> + <family lang="und-Cari"> + <font weight="400" style="normal" postScriptName="NotoSansCarian"> + NotoSansCarian-Regular.ttf + </font> + </family> + <family lang="und-Cakm"> + <font weight="400" style="normal">NotoSansChakma-Regular.otf</font> + </family> + <family lang="und-Cher"> + <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> + </family> + <family lang="und-Copt"> + <font weight="400" style="normal" postScriptName="NotoSansCoptic"> + NotoSansCoptic-Regular.ttf + </font> + </family> + <family lang="und-Xsux"> + <font weight="400" style="normal" postScriptName="NotoSansCuneiform"> + NotoSansCuneiform-Regular.ttf + </font> + </family> + <family lang="und-Cprt"> + <font weight="400" style="normal" postScriptName="NotoSansCypriot"> + NotoSansCypriot-Regular.ttf + </font> + </family> + <family lang="und-Dsrt"> + <font weight="400" style="normal" postScriptName="NotoSansDeseret"> + NotoSansDeseret-Regular.ttf + </font> + </family> + <family lang="und-Egyp"> + <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs"> + NotoSansEgyptianHieroglyphs-Regular.ttf + </font> + </family> + <family lang="und-Elba"> + <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font> + </family> + <family lang="und-Glag"> + <font weight="400" style="normal" postScriptName="NotoSansGlagolitic"> + NotoSansGlagolitic-Regular.ttf + </font> + </family> + <family lang="und-Goth"> + <font weight="400" style="normal" postScriptName="NotoSansGothic"> + NotoSansGothic-Regular.ttf + </font> + </family> + <family lang="und-Hano"> + <font weight="400" style="normal" postScriptName="NotoSansHanunoo"> + NotoSansHanunoo-Regular.ttf + </font> + </family> + <family lang="und-Armi"> + <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic"> + NotoSansImperialAramaic-Regular.ttf + </font> + </family> + <family lang="und-Phli"> + <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi"> + NotoSansInscriptionalPahlavi-Regular.ttf + </font> + </family> + <family lang="und-Prti"> + <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian"> + NotoSansInscriptionalParthian-Regular.ttf + </font> + </family> + <family lang="und-Java"> + <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font> + </family> + <family lang="und-Kthi"> + <font weight="400" style="normal" postScriptName="NotoSansKaithi"> + NotoSansKaithi-Regular.ttf + </font> + </family> + <family lang="und-Kali"> + <font weight="400" style="normal" postScriptName="NotoSansKayahLi"> + NotoSansKayahLi-Regular.ttf + </font> + </family> + <family lang="und-Khar"> + <font weight="400" style="normal" postScriptName="NotoSansKharoshthi"> + NotoSansKharoshthi-Regular.ttf + </font> + </family> + <family lang="und-Lepc"> + <font weight="400" style="normal" postScriptName="NotoSansLepcha"> + NotoSansLepcha-Regular.ttf + </font> + </family> + <family lang="und-Limb"> + <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf + </font> + </family> + <family lang="und-Linb"> + <font weight="400" style="normal" postScriptName="NotoSansLinearB"> + NotoSansLinearB-Regular.ttf + </font> + </family> + <family lang="und-Lisu"> + <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf + </font> + </family> + <family lang="und-Lyci"> + <font weight="400" style="normal" postScriptName="NotoSansLycian"> + NotoSansLycian-Regular.ttf + </font> + </family> + <family lang="und-Lydi"> + <font weight="400" style="normal" postScriptName="NotoSansLydian"> + NotoSansLydian-Regular.ttf + </font> + </family> + <family lang="und-Mand"> + <font weight="400" style="normal" postScriptName="NotoSansMandaic"> + NotoSansMandaic-Regular.ttf + </font> + </family> + <family lang="und-Mtei"> + <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek"> + NotoSansMeeteiMayek-Regular.ttf + </font> + </family> + <family lang="und-Talu"> + <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue"> + NotoSansNewTaiLue-Regular.ttf + </font> + </family> + <family lang="und-Nkoo"> + <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf + </font> + </family> + <family lang="und-Ogam"> + <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf + </font> + </family> + <family lang="und-Olck"> + <font weight="400" style="normal" postScriptName="NotoSansOlChiki"> + NotoSansOlChiki-Regular.ttf + </font> + </family> + <family lang="und-Ital"> + <font weight="400" style="normal" postScriptName="NotoSansOldItalic"> + NotoSansOldItalic-Regular.ttf + </font> + </family> + <family lang="und-Xpeo"> + <font weight="400" style="normal" postScriptName="NotoSansOldPersian"> + NotoSansOldPersian-Regular.ttf + </font> + </family> + <family lang="und-Sarb"> + <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian"> + NotoSansOldSouthArabian-Regular.ttf + </font> + </family> + <family lang="und-Orkh"> + <font weight="400" style="normal" postScriptName="NotoSansOldTurkic"> + NotoSansOldTurkic-Regular.ttf + </font> + </family> + <family lang="und-Osge"> + <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font> + </family> + <family lang="und-Osma"> + <font weight="400" style="normal" postScriptName="NotoSansOsmanya"> + NotoSansOsmanya-Regular.ttf + </font> + </family> + <family lang="und-Phnx"> + <font weight="400" style="normal" postScriptName="NotoSansPhoenician"> + NotoSansPhoenician-Regular.ttf + </font> + </family> + <family lang="und-Rjng"> + <font weight="400" style="normal" postScriptName="NotoSansRejang"> + NotoSansRejang-Regular.ttf + </font> + </family> + <family lang="und-Runr"> + <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf + </font> + </family> + <family lang="und-Samr"> + <font weight="400" style="normal" postScriptName="NotoSansSamaritan"> + NotoSansSamaritan-Regular.ttf + </font> + </family> + <family lang="und-Saur"> + <font weight="400" style="normal" postScriptName="NotoSansSaurashtra"> + NotoSansSaurashtra-Regular.ttf + </font> + </family> + <family lang="und-Shaw"> + <font weight="400" style="normal" postScriptName="NotoSansShavian"> + NotoSansShavian-Regular.ttf + </font> + </family> + <family lang="und-Sund"> + <font weight="400" style="normal" postScriptName="NotoSansSundanese"> + NotoSansSundanese-Regular.ttf + </font> + </family> + <family lang="und-Sylo"> + <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri"> + NotoSansSylotiNagri-Regular.ttf + </font> + </family> + <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. --> + <family lang="und-Syre"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela"> + NotoSansSyriacEstrangela-Regular.ttf + </font> + </family> + <family lang="und-Syrn"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern"> + NotoSansSyriacEastern-Regular.ttf + </font> + </family> + <family lang="und-Syrj"> + <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern"> + NotoSansSyriacWestern-Regular.ttf + </font> + </family> + <family lang="und-Tglg"> + <font weight="400" style="normal" postScriptName="NotoSansTagalog"> + NotoSansTagalog-Regular.ttf + </font> + </family> + <family lang="und-Tagb"> + <font weight="400" style="normal" postScriptName="NotoSansTagbanwa"> + NotoSansTagbanwa-Regular.ttf + </font> + </family> + <family lang="und-Lana"> + <font weight="400" style="normal" postScriptName="NotoSansTaiTham"> + NotoSansTaiTham-Regular.ttf + </font> + </family> + <family lang="und-Tavt"> + <font weight="400" style="normal" postScriptName="NotoSansTaiViet"> + NotoSansTaiViet-Regular.ttf + </font> + </family> + <family lang="und-Tibt"> + <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular"> + NotoSerifTibetan-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular"> + NotoSerifTibetan-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular"> + NotoSerifTibetan-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular"> + NotoSerifTibetan-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Tfng"> + <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font> + </family> + <family lang="und-Ugar"> + <font weight="400" style="normal" postScriptName="NotoSansUgaritic"> + NotoSansUgaritic-Regular.ttf + </font> + </family> + <family lang="und-Vaii"> + <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf + </font> + </family> + <family> + <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> + </family> + <family lang="zh-Hans"> + <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="2" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="zh-Hant,zh-Bopo"> + <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="3" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="ja"> + <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="0" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="ko"> + <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="100"/> + </font> + <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="200"/> + </font> + <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="300"/> + </font> + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="700"/> + </font> + <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="800"/> + </font> + <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + NotoSansCJK-Regular.ttc + <axis tag="wght" stylevalue="900"/> + </font> + <font weight="400" style="normal" index="1" fallbackFor="serif" + postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc + </font> + </family> + <family lang="und-Zsye" ignore="true"> + <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">NotoColorEmoji.ttf</font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font> + </family> + <family lang="und-Zsym"> + <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font> + </family> + <!-- + Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't + override the East Asian punctuation for Chinese. + --> + <family lang="und-Tale"> + <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf + </font> + </family> + <family lang="und-Yiii"> + <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font> + </family> + <family lang="und-Mong"> + <font weight="400" style="normal" postScriptName="NotoSansMongolian"> + NotoSansMongolian-Regular.ttf + </font> + </family> + <family lang="und-Phag"> + <font weight="400" style="normal" postScriptName="NotoSansPhagsPa"> + NotoSansPhagsPa-Regular.ttf + </font> + </family> + <family lang="und-Hluw"> + <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font> + </family> + <family lang="und-Bass"> + <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font> + </family> + <family lang="und-Bhks"> + <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font> + </family> + <family lang="und-Hatr"> + <font weight="400" style="normal">NotoSansHatran-Regular.otf</font> + </family> + <family lang="und-Lina"> + <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font> + </family> + <family lang="und-Mani"> + <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font> + </family> + <family lang="und-Marc"> + <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font> + </family> + <family lang="und-Merc"> + <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font> + </family> + <family lang="und-Plrd"> + <font weight="400" style="normal">NotoSansMiao-Regular.otf</font> + </family> + <family lang="und-Mroo"> + <font weight="400" style="normal">NotoSansMro-Regular.otf</font> + </family> + <family lang="und-Mult"> + <font weight="400" style="normal">NotoSansMultani-Regular.otf</font> + </family> + <family lang="und-Nbat"> + <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font> + </family> + <family lang="und-Newa"> + <font weight="400" style="normal">NotoSansNewa-Regular.otf</font> + </family> + <family lang="und-Narb"> + <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font> + </family> + <family lang="und-Perm"> + <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font> + </family> + <family lang="und-Hmng"> + <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font> + </family> + <family lang="und-Palm"> + <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font> + </family> + <family lang="und-Pauc"> + <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font> + </family> + <family lang="und-Shrd"> + <font weight="400" style="normal">NotoSansSharada-Regular.otf</font> + </family> + <family lang="und-Sora"> + <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font> + </family> + <family lang="und-Gong"> + <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font> + </family> + <family lang="und-Rohg"> + <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font> + </family> + <family lang="und-Khoj"> + <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font> + </family> + <family lang="und-Gonm"> + <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font> + </family> + <family lang="und-Wcho"> + <font weight="400" style="normal">NotoSansWancho-Regular.otf</font> + </family> + <family lang="und-Wara"> + <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font> + </family> + <family lang="und-Gran"> + <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font> + </family> + <family lang="und-Modi"> + <font weight="400" style="normal">NotoSansModi-Regular.ttf</font> + </family> + <family lang="und-Dogr"> + <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font> + </family> + <family lang="und-Medf"> + <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> + NotoSansMedefaidrin-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> + NotoSansMedefaidrin-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> + NotoSansMedefaidrin-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular"> + NotoSansMedefaidrin-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Soyo"> + <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular"> + NotoSansSoyombo-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular"> + NotoSansSoyombo-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular"> + NotoSansSoyombo-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular"> + NotoSansSoyombo-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Takr"> + <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular"> + NotoSansTakri-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular"> + NotoSansTakri-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular"> + NotoSansTakri-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular"> + NotoSansTakri-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Hmnp"> + <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> + NotoSerifNyiakengPuachueHmong-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> + NotoSerifNyiakengPuachueHmong-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> + NotoSerifNyiakengPuachueHmong-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular"> + NotoSerifNyiakengPuachueHmong-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> + <family lang="und-Yezi"> + <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular"> + NotoSerifYezidi-VF.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular"> + NotoSerifYezidi-VF.ttf + <axis tag="wght" stylevalue="500"/> + </font> + <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular"> + NotoSerifYezidi-VF.ttf + <axis tag="wght" stylevalue="600"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular"> + NotoSerifYezidi-VF.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> +</familyset> diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index f6ba103f6f05..ae61a2d811d2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -17,6 +17,7 @@ package android.graphics; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; +import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION; import android.annotation.ColorInt; import android.annotation.ColorLong; @@ -133,7 +134,9 @@ public class Paint { FAKE_BOLD_TEXT_FLAG, LINEAR_TEXT_FLAG, SUBPIXEL_TEXT_FLAG, - EMBEDDED_BITMAP_TEXT_FLAG + EMBEDDED_BITMAP_TEXT_FLAG, + TEXT_RUN_FLAG_LEFT_EDGE, + TEXT_RUN_FLAG_RIGHT_EDGE }) public @interface PaintFlag{} @@ -264,6 +267,66 @@ public class Paint { /** @hide bit mask for the flag enabling vertical rendering for text */ public static final int VERTICAL_TEXT_FLAG = 0x1000; + /** + * A text run flag that indicates the run is located the visually most left segment of the line. + * <p> + * This flag is used for telling the underlying text layout engine that the text is located at + * the most left of the line. This flag is used for controlling the amount letter spacing + * added. If the text is in the middle of the line, the text layout engine assigns additional + * letter spacing to the both side of each letter. On the other hand, the letter spacing should + * not be added to the visually most left and right of the line. By setting this flag, text + * layout engine calculates the layout as it is located at the most visually left of the line + * and doesn't add letter spacing to the left of this run. + * <p> + * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only + * if the target run is located visually most left position. This left does not always mean the + * beginning of the text. + * <p> + * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well. + * <p> + * Note that this flag is only effective for run based APIs. For example, this flag works for + * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} + * and + * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}. + * However, this doesn't work for + * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or + * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both + * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000; + + + /** + * A text run flag that indicates the run is located the visually most right segment of the + * line. + * <p> + * This flag is used for telling the underlying text layout engine that the text is located at + * the most right of the line. This flag is used for controlling the amount letter spacing + * added. If the text is in the middle of the line, the text layout engine assigns additional + * letter spacing to the both side of each letter. On the other hand, the letter spacing should + * not be added to the visually most left and right of the line. By setting this flag, text + * layout engine calculates the layout as it is located at the most visually left of the line + * and doesn't add letter spacing to the left of this run. + * <p> + * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only + * if the target run is located visually most right position. This right does not always mean + * the end of the text. + * <p> + * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well. + * <p> + * Note that this flag is only effective for run based APIs. For example, this flag works for + * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)} + * and + * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}. + * However, this doesn't work for + * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or + * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both + * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified. + */ + @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION) + public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000; + // These flags are always set on a new/reset paint, even if flags 0 is passed. static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG | FILTER_BITMAP_FLAG; @@ -2520,17 +2583,24 @@ public class Paint { if (text.length == 0 || count == 0) { return 0f; } - if (!mHasCompatScaling) { - return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, - index, count, index, count, mBidiFlags, null, 0)); - } + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count, - mBidiFlags, null, 0); - setTextSize(oldSize); - return (float) Math.ceil(w*mInvCompatScaling); + if (!mHasCompatScaling) { + return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, + index, count, index, count, mBidiFlags, null, 0)); + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count, + mBidiFlags, null, 0); + setTextSize(oldSize); + return (float) Math.ceil(w * mInvCompatScaling); + } finally { + setFlags(oldFlag); + } } /** @@ -2552,16 +2622,22 @@ public class Paint { if (text.length() == 0 || start == end) { return 0f; } - if (!mHasCompatScaling) { - return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, - start, end, start, end, mBidiFlags, null, 0)); + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + return (float) Math.ceil(nGetTextAdvances(mNativePaint, text, + start, end, start, end, mBidiFlags, null, 0)); + } + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); + final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, + null, 0); + setTextSize(oldSize); + return (float) Math.ceil(w * mInvCompatScaling); + } finally { + setFlags(oldFlag); } - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, - null, 0); - setTextSize(oldSize); - return (float) Math.ceil(w * mInvCompatScaling); } /** @@ -2766,19 +2842,26 @@ public class Paint { if (text.length == 0 || count == 0) { return 0; } - if (!mHasCompatScaling) { + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, + 0); + return count; + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0); + setTextSize(oldSize); + for (int i = 0; i < count; i++) { + widths[i] *= mInvCompatScaling; + } return count; + } finally { + setFlags(oldFlag); } - - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0); - setTextSize(oldSize); - for (int i = 0; i < count; i++) { - widths[i] *= mInvCompatScaling; - } - return count; } /** @@ -2849,19 +2932,25 @@ public class Paint { if (text.length() == 0 || start == end) { return 0; } - if (!mHasCompatScaling) { + int oldFlag = getFlags(); + setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE)); + try { + if (!mHasCompatScaling) { + nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); + return end - start; + } + + final float oldSize = getTextSize(); + setTextSize(oldSize * mCompatScaling); nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); + setTextSize(oldSize); + for (int i = 0; i < end - start; i++) { + widths[i] *= mInvCompatScaling; + } return end - start; + } finally { + setFlags(oldFlag); } - - final float oldSize = getTextSize(); - setTextSize(oldSize * mCompatScaling); - nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0); - setTextSize(oldSize); - for (int i = 0; i < end - start; i++) { - widths[i] *= mInvCompatScaling; - } - return end - start; } /** diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 2d33e8d24ece..6da07198c3ad 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -269,6 +269,10 @@ public class MeasuredText { * offset is zero. After the style is applied the internal offset is moved to {@code offset * + length}, and next call will start from this new position. * + * <p> + * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are + * ignored and treated as both of them are set. + * * @param paint a paint * @param length a length to be applied with a given paint, can not exceed the length of the * text diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 45540e0fbbb8..4cdc06a999a7 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -162,6 +162,7 @@ android_library { "com_android_wm_shell_flags_lib", "com.android.window.flags.window-aconfig-java", "WindowManager-Shell-proto", + "perfetto_trace_java_protos", "dagger2", "jsr330", ], diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 901d5fa0cd9a..0e046581cb48 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -1,13 +1,6 @@ package: "com.android.wm.shell" flag { - name: "example_flag" - namespace: "multitasking" - description: "An Example Flag" - bug: "300136750" -} - -flag { name: "enable_app_pairs" namespace: "multitasking" description: "Enables the ability to create and save app pairs to the Home screen" diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index e4abae48c8fd..9854e58dd7cf 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -134,6 +134,13 @@ <!-- Whether the additional education about reachability is enabled --> <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool> + <!-- The minimum tolerance of the percentage of activity bounds within its task to hide + size compat restart button. Value lower than 0 or higher than 100 will be ignored. + 100 is the default value where the activity has to fit exactly within the task to allow + size compat restart button to be hidden. 0 means size compat restart button will always + be hidden. --> + <integer name="config_letterboxRestartButtonHideTolerance">100</integer> + <!-- Whether DragAndDrop capability is enabled --> <bool name="config_enableShellDragDrop">true</bool> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index e48732801094..bb0dd95b042f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -769,8 +769,10 @@ public class StackAnimationController extends boolean swapped = false; for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) { View view = bubbleViews.get(newIndex); - final int oldIndex = mLayout.indexOfChild(view); - swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + if (view != null) { + final int oldIndex = mLayout.indexOfChild(view); + swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after); + } } if (!swapped) { // All bubbles were at the right position. Make sure badges and z order is correct. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java index 09d99b204bdb..fa2e23647a39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -71,6 +71,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX = "has_seen_vertical_reachability_education"; + private static final int MAX_PERCENTAGE_VAL = 100; + /** * The {@link SharedPreferences} instance for the restart dialog and the reachability * education. @@ -82,6 +84,12 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi */ private final SharedPreferences mLetterboxEduSharedPreferences; + /** + * The minimum tolerance of the percentage of activity bounds within its task to hide + * size compat restart button. + */ + private final int mHideSizeCompatRestartButtonTolerance; + // Whether the extended restart dialog is enabled private boolean mIsRestartDialogEnabled; @@ -106,6 +114,9 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi R.bool.config_letterboxIsRestartDialogEnabled); mIsReachabilityEducationEnabled = context.getResources().getBoolean( R.bool.config_letterboxIsReachabilityEducationEnabled); + final int tolerance = context.getResources().getInteger( + R.integer.config_letterboxRestartButtonHideTolerance); + mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance); mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG); @@ -179,6 +190,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi || !hasSeenVerticalReachabilityEducation(taskInfo)); } + int getHideSizeCompatRestartButtonTolerance() { + return mHideSizeCompatRestartButtonTolerance; + } + boolean getHasSeenLetterboxEducation(int userId) { return mLetterboxEduSharedPreferences .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false); @@ -218,6 +233,15 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi } } + // Returns the minimum tolerance of the percentage of activity bounds within its task to hide + // size compat restart button. Value lower than 0 or higher than 100 will be ignored. + // 100 is the default value where the activity has to fit exactly within the task to allow + // size compat restart button to be hidden. 0 means size compat restart button will always + // be hidden. + private int getHideSizeCompatRestartButtonTolerance(int tolerance) { + return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance; + } + private boolean isReachabilityEducationEnabled() { return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled && mIsLetterboxReachabilityEducationAllowed); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 00e0cdb034b6..2dd27430e348 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -22,7 +22,9 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPL import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AppCompatTaskInfo; import android.app.AppCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.Context; @@ -33,6 +35,7 @@ import android.view.LayoutInflater; import android.view.View; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; @@ -68,6 +71,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @VisibleForTesting CompatUILayout mLayout; + private final float mHideScmTolerance; + CompatUIWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, CompatUICallback callback, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, @@ -75,11 +80,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; - mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat + && shouldShowSizeCompatRestartButton(taskInfo); mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; mCompatUIConfiguration = compatUIConfiguration; mOnRestartButtonClicked = onRestartButtonClicked; + mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance(); } @Override @@ -107,6 +114,11 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { mLayout = inflateLayout(); mLayout.inject(this); + final TaskInfo taskInfo = getLastTaskInfo(); + if (taskInfo != null) { + mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo); + } + updateVisibilityOfViews(); if (mHasSizeCompat) { @@ -127,7 +139,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { boolean canShow) { final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; - mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat + && shouldShowSizeCompatRestartButton(taskInfo); mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState; if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) { @@ -208,6 +221,30 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { updateSurfacePosition(positionX, positionY); } + @VisibleForTesting + boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) { + if (!Flags.allowHideScmButton()) { + return true; + } + final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo; + final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth, + appCompatTaskInfo.topActivityLetterboxHeight); + final int taskArea = computeArea(taskBounds.width(), taskBounds.height()); + if (letterboxArea == 0 || taskArea == 0) { + return false; + } + final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100; + return percentageAreaOfLetterboxInTask < mHideScmTolerance; + } + + private int computeArea(int width, int height) { + if (width == 0 || height == 0) { + return 0; + } + return width * height; + } + private void updateVisibilityOfViews() { if (mLayout == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 95d7ad5c416f..c3a82ce258df 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -99,7 +99,11 @@ class DragToDesktopTransitionHandler( windowDecoration: DesktopModeWindowDecoration ) { if (inProgress) { - error("A drag to desktop is already in progress") + KtProtoLog.v( + ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + "DragToDesktop: Drag to desktop transition already in progress." + ) + return } val options = ActivityOptions.makeBasic().apply { @@ -144,6 +148,12 @@ class DragToDesktopTransitionHandler( * inside the desktop drop zone. */ fun finishDragToDesktopTransition(wct: WindowContainerTransaction) { + if (!inProgress) { + // Don't attempt to finish a drag to desktop transition since there is no transition in + // progress which means that the drag to desktop transition was never successfully + // started. + return + } if (requireTransitionState().startAborted) { // Don't attempt to complete the drag-to-desktop since the start transition didn't // succeed as expected. Just reset the state as if nothing happened. @@ -161,6 +171,12 @@ class DragToDesktopTransitionHandler( * means the user wants to remain in their current windowing mode. */ fun cancelDragToDesktopTransition() { + if (!inProgress) { + // Don't attempt to cancel a drag to desktop transition since there is no transition in + // progress which means that the drag to desktop transition was never successfully + // started. + return + } val state = requireTransitionState() if (state.startAborted) { // Don't attempt to cancel the drag-to-desktop since the start transition didn't diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index b0d8b47b170a..3fb0dbfaa63d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -83,6 +83,9 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.tracing.LegacyTransitionTracer; +import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer; +import com.android.wm.shell.transition.tracing.TransitionTracer; import com.android.wm.shell.util.TransitionUtil; import java.io.PrintWriter; @@ -184,7 +187,7 @@ public class Transitions implements RemoteCallable<Transitions>, private final ShellController mShellController; private final ShellTransitionImpl mImpl = new ShellTransitionImpl(); private final SleepHandler mSleepHandler = new SleepHandler(); - private final Tracer mTracer = new Tracer(); + private final TransitionTracer mTransitionTracer; private boolean mIsRegistered = false; /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ @@ -307,6 +310,12 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote"); shellInit.addInitCallback(this::onInit, this); mHomeTransitionObserver = observer; + + if (android.tracing.Flags.perfettoTransitionTracing()) { + mTransitionTracer = new PerfettoTransitionTracer(); + } else { + mTransitionTracer = new LegacyTransitionTracer(); + } } private void onInit() { @@ -868,7 +877,7 @@ public class Transitions implements RemoteCallable<Transitions>, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" + " %s is still animating. Notify the animating transition" + " in case they can be merged", ready, playing); - mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); + mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, playing.mToken, (wct) -> onMerged(playing, ready)); } @@ -902,7 +911,7 @@ public class Transitions implements RemoteCallable<Transitions>, for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken); } - mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId()); + mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId()); // See if we should merge another transition. processReadyQueue(track); } @@ -923,7 +932,7 @@ public class Transitions implements RemoteCallable<Transitions>, active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct)); if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); - mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); + mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler); return; } } @@ -948,7 +957,7 @@ public class Transitions implements RemoteCallable<Transitions>, if (consumed) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); - mTracer.logDispatched(info.getDebugId(), mHandlers.get(i)); + mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i)); return mHandlers.get(i); } } @@ -978,7 +987,7 @@ public class Transitions implements RemoteCallable<Transitions>, final Track track = mTracks.get(transition.getTrack()); transition.mAborted = true; - mTracer.logAborted(transition.mInfo.getDebugId()); + mTransitionTracer.logAborted(transition.mInfo.getDebugId()); if (transition.mHandler != null) { // Notifies to clean-up the aborted transition. @@ -1506,12 +1515,18 @@ public class Transitions implements RemoteCallable<Transitions>, } } - @Override public boolean onShellCommand(String[] args, PrintWriter pw) { switch (args[0]) { case "tracing": { - mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw); + if (!android.tracing.Flags.perfettoTransitionTracing()) { + ((LegacyTransitionTracer) mTransitionTracer) + .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw); + } else { + pw.println("Command not supported. Use the Perfetto command instead to start " + + "and stop this trace instead."); + return false; + } return true; } default: { @@ -1524,8 +1539,10 @@ public class Transitions implements RemoteCallable<Transitions>, @Override public void printShellCommandHelp(PrintWriter pw, String prefix) { - pw.println(prefix + "tracing"); - mTracer.printShellCommandHelp(pw, prefix + " "); + if (!android.tracing.Flags.perfettoTransitionTracing()) { + pw.println(prefix + "tracing"); + ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + " "); + } } private void dump(@NonNull PrintWriter pw, String prefix) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java index 5919aad133c7..9c848869e0f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.transition; +package com.android.wm.shell.transition.tracing; import static android.os.Build.IS_USER; @@ -29,6 +29,7 @@ import android.util.Log; import com.android.internal.util.TraceBuffer; import com.android.wm.shell.sysui.ShellCommandHandler; +import com.android.wm.shell.transition.Transitions; import com.google.protobuf.nano.MessageNano; @@ -45,7 +46,8 @@ import java.util.concurrent.TimeUnit; /** * Helper class to collect and dump transition traces. */ -public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { +public class LegacyTransitionTracer + implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer { private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB @@ -60,33 +62,33 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { private final TraceBuffer.ProtoProvider mProtoProvider = new TraceBuffer.ProtoProvider<MessageNano, - com.android.wm.shell.nano.WmShellTransitionTraceProto, - com.android.wm.shell.nano.Transition>() { - @Override - public int getItemSize(MessageNano proto) { - return proto.getCachedSize(); - } - - @Override - public byte[] getBytes(MessageNano proto) { - return MessageNano.toByteArray(proto); - } - - @Override - public void write( - com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto, - Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os) + com.android.wm.shell.nano.WmShellTransitionTraceProto, + com.android.wm.shell.nano.Transition>() { + @Override + public int getItemSize(MessageNano proto) { + return proto.getCachedSize(); + } + + @Override + public byte[] getBytes(MessageNano proto) { + return MessageNano.toByteArray(proto); + } + + @Override + public void write( + com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto, + Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os) throws IOException { - encapsulatingProto.transitions = buffer.toArray( - new com.android.wm.shell.nano.Transition[0]); - os.write(getBytes(encapsulatingProto)); - } - }; + encapsulatingProto.transitions = buffer.toArray( + new com.android.wm.shell.nano.Transition[0]); + os.write(getBytes(encapsulatingProto)); + } + }; private final TraceBuffer<MessageNano, com.android.wm.shell.nano.WmShellTransitionTraceProto, - com.android.wm.shell.nano.Transition> mTraceBuffer - = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider, - (proto) -> handleOnEntryRemovedFromTrace(proto)); + com.android.wm.shell.nano.Transition> mTraceBuffer = + new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider, + this::handleOnEntryRemovedFromTrace); private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>(); private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>(); @@ -99,6 +101,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { * @param transitionId The id of the transition being dispatched. * @param handler The handler the transition is being dispatched to. */ + @Override public void logDispatched(int transitionId, Transitions.TransitionHandler handler) { final int handlerId; if (mHandlerIds.containsKey(handler)) { @@ -130,6 +133,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { * * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged. */ + @Override public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) { com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition(); proto.id = mergeRequestedTransitionId; @@ -145,6 +149,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { * @param mergedTransitionId The id of the transition that was merged. * @param playingTransitionId The id of the transition the transition was merged into. */ + @Override public void logMerged(int mergedTransitionId, int playingTransitionId) { com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition(); proto.id = mergedTransitionId; @@ -159,6 +164,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler { * * @param transitionId The id of the transition that was aborted. */ + @Override public void logAborted(int transitionId) { com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition(); proto.id = transitionId; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java new file mode 100644 index 000000000000..99df6a31beac --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2023 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.wm.shell.transition.tracing; + +import android.internal.perfetto.protos.PerfettoTrace; +import android.os.SystemClock; +import android.tracing.perfetto.DataSourceInstance; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.tracing.perfetto.TracingContext; +import android.tracing.transition.TransitionDataSource; +import android.util.proto.ProtoOutputStream; + +import com.android.wm.shell.transition.Transitions; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Helper class to collect and dump transition traces. + */ +public class PerfettoTransitionTracer implements TransitionTracer { + private final AtomicInteger mActiveTraces = new AtomicInteger(0); + private final TransitionDataSource mDataSource = new TransitionDataSource( + mActiveTraces::incrementAndGet, + this::onFlush, + mActiveTraces::decrementAndGet); + + public PerfettoTransitionTracer() { + Producer.init(InitArguments.DEFAULTS); + mDataSource.register(DataSourceParams.DEFAULTS); + } + + /** + * Adds an entry in the trace to log that a transition has been dispatched to a handler. + * + * @param transitionId The id of the transition being dispatched. + * @param handler The handler the transition is being dispatched to. + */ + @Override + public void logDispatched(int transitionId, Transitions.TransitionHandler handler) { + if (!isTracing()) { + return; + } + + mDataSource.trace(ctx -> { + final int handlerId = getHandlerId(handler, ctx); + + final ProtoOutputStream os = ctx.newTracePacket(); + final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); + os.write(PerfettoTrace.ShellTransition.ID, transitionId); + os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS, + SystemClock.elapsedRealtimeNanos()); + os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId); + os.end(token); + }); + } + + private static int getHandlerId(Transitions.TransitionHandler handler, + TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) { + final Map<String, Integer> handlerMapping = + ctx.getCustomTlsState().handlerMapping; + final int handlerId; + if (handlerMapping.containsKey(handler.getClass().getName())) { + handlerId = handlerMapping.get(handler.getClass().getName()); + } else { + // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto + handlerId = handlerMapping.size() + 1; + handlerMapping.put(handler.getClass().getName(), handlerId); + } + return handlerId; + } + + /** + * Adds an entry in the trace to log that a request to merge a transition was made. + * + * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged. + */ + @Override + public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) { + if (!isTracing()) { + return; + } + + mDataSource.trace(ctx -> { + final ProtoOutputStream os = ctx.newTracePacket(); + final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); + os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId); + os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS, + SystemClock.elapsedRealtimeNanos()); + os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId); + os.end(token); + }); + } + + /** + * Adds an entry in the trace to log that a transition was merged by the handler. + * + * @param mergedTransitionId The id of the transition that was merged. + * @param playingTransitionId The id of the transition the transition was merged into. + */ + @Override + public void logMerged(int mergedTransitionId, int playingTransitionId) { + if (!isTracing()) { + return; + } + + mDataSource.trace(ctx -> { + final ProtoOutputStream os = ctx.newTracePacket(); + final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); + os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId); + os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS, + SystemClock.elapsedRealtimeNanos()); + os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId); + os.end(token); + }); + } + + /** + * Adds an entry in the trace to log that a transition was aborted. + * + * @param transitionId The id of the transition that was aborted. + */ + @Override + public void logAborted(int transitionId) { + if (!isTracing()) { + return; + } + + mDataSource.trace(ctx -> { + final ProtoOutputStream os = ctx.newTracePacket(); + final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION); + os.write(PerfettoTrace.ShellTransition.ID, transitionId); + os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS, + SystemClock.elapsedRealtimeNanos()); + os.end(token); + }); + } + + private boolean isTracing() { + return mActiveTraces.get() > 0; + } + + private void onFlush() { + mDataSource.trace(ctx -> { + final ProtoOutputStream os = ctx.newTracePacket(); + + final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping; + for (String handler : handlerMapping.keySet()) { + final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS); + os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler)); + os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler); + os.end(token); + } + + ctx.flush(); + }); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java new file mode 100644 index 000000000000..5857ad88e9e6 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 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.wm.shell.transition.tracing; + +import com.android.wm.shell.transition.Transitions; + +public interface TransitionTracer { + /** + * Adds an entry in the trace to log that a transition has been dispatched to a handler. + * + * @param transitionId The id of the transition being dispatched. + * @param handler The handler the transition is being dispatched to. + */ + void logDispatched(int transitionId, Transitions.TransitionHandler handler); + + /** + * Adds an entry in the trace to log that a request to merge a transition was made. + * + * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged. + */ + void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId); + + /** + * Adds an entry in the trace to log that a transition was merged by the handler. + * + * @param mergedTransitionId The id of the transition that was merged. + * @param playingTransitionId The id of the transition the transition was merged into. + */ + void logMerged(int mergedTransitionId, int playingTransitionId); + + /** + * Adds an entry in the trace to log that a transition was aborted. + * + * @param transitionId The id of the transition that was aborted. + */ + void logAborted(int transitionId); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 1a793a16f254..b2eeea7048bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -271,6 +271,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); } + final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + // If a decor's resize drag zone is active, don't also try to reposition it. + if (decoration.isHandlingDragResize()) break; final int dragPointerIdx = e.findPointerIndex(mDragPointerId); mDragPositioningCallback.onDragPositioningMove( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 1debb02e86af..5a74255df49a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -286,6 +286,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL closeBackground.setTintList(buttonTintColor); } + boolean isHandlingDragResize() { + return mDragResizeListener.isHandlingDragResize(); + } + private void closeDragResizeListener() { if (mDragResizeListener == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index aabc1cfb5875..554b1fb99550 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -491,8 +491,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return true; } case MotionEvent.ACTION_MOVE: { + mShouldClick = false; final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); + // If a decor's resize drag zone is active, don't also try to reposition it. + if (decoration.isHandlingDragResize()) break; decoration.closeMaximizeMenu(); if (e.findPointerIndex(mDragPointerId) == -1) { mDragPointerId = e.getPointerId(0); @@ -505,7 +508,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { e.getRawX(dragPointerIdx), newTaskBounds)); mIsDragging = true; - mShouldClick = false; return true; } case MotionEvent.ACTION_UP: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 53f806ccabee..20233331997f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -387,6 +387,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return mHandleMenu != null; } + boolean isHandlingDragResize() { + return mDragResizeListener.isHandlingDragResize(); + } + private void loadAppInfo() { String packageName = mTaskInfo.realActivity.getPackageName(); PackageManager pm = mContext.getApplicationContext().getPackageManager(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8511a21d4294..8b38f991a2db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -320,6 +320,10 @@ class DragResizeInputListener implements AutoCloseable { } } + boolean isHandlingDragResize() { + return mInputEventReceiver.isHandlingEvents(); + } + @Override public void close() { mInputEventReceiver.dispose(); @@ -386,6 +390,10 @@ class DragResizeInputListener implements AutoCloseable { finishInputEvent(inputEvent, handleInputEvent(inputEvent)); } + boolean isHandlingEvents() { + return mShouldHandleEvents; + } + private boolean handleInputEvent(InputEvent inputEvent) { if (!(inputEvent instanceof MotionEvent)) { return false; @@ -409,7 +417,6 @@ class DragResizeInputListener implements AutoCloseable { mShouldHandleEvents = isInResizeHandleBounds(x, y); } if (mShouldHandleEvents) { - mInputManager.pilferPointers(mInputChannel.getToken()); mDragPointerId = e.getPointerId(0); float rawX = e.getRawX(0); float rawY = e.getRawY(0); @@ -427,6 +434,7 @@ class DragResizeInputListener implements AutoCloseable { if (!mShouldHandleEvents) { break; } + mInputManager.pilferPointers(mInputChannel.getToken()); int dragPointerIndex = e.findPointerIndex(mDragPointerId); float rawX = e.getRawX(dragPointerIndex); float rawY = e.getRawY(dragPointerIndex); @@ -437,6 +445,7 @@ class DragResizeInputListener implements AutoCloseable { } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { + mInputManager.pilferPointers(mInputChannel.getToken()); if (mShouldHandleEvents) { int dragPointerIndex = e.findPointerIndex(mDragPointerId); final Rect taskBounds = mCallback.onDragPositioningEnd( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index c1b18f959641..7c6e69eb1ec9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -61,6 +61,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @Surface.Rotation private int mRotation; + private boolean mVeilIsVisible; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, @@ -94,7 +95,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); if (isResizing()) { - mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -119,8 +119,13 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, mDisplayController, mDesktopWindowDecoration)) { - mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); mIsResizingOrAnimatingResize = true; + if (!mVeilIsVisible) { + mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds); + mVeilIsVisible = true; + } else { + mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); + } } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, @@ -143,7 +148,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); mTransitions.startTransition(TRANSIT_CHANGE, wct, this); - } else { + } else if (mVeilIsVisible) { // If bounds haven't changed, perform necessary veil reset here as startAnimation // won't be called. mDesktopWindowDecoration.hideResizeVeil(); @@ -163,6 +168,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback, mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); + mVeilIsVisible = false; return new Rect(mRepositionTaskBounds); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index d4b97ed55192..2acfd83084ab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -20,6 +20,8 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.WindowInsets.Type.navigationBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -27,6 +29,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; @@ -39,6 +42,7 @@ import android.app.AppCompatTaskInfo; import android.app.TaskInfo; import android.content.res.Configuration; import android.graphics.Rect; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayInfo; @@ -50,6 +54,7 @@ import android.view.View; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; @@ -59,6 +64,7 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -76,6 +82,8 @@ import java.util.function.Consumer; @RunWith(AndroidTestingRunner.class) @SmallTest public class CompatUIWindowManagerTest extends ShellTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); private static final int TASK_ID = 1; @@ -107,6 +115,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { public void testCreateSizeCompatButton() { // Doesn't create layout if show is false. mWindowManager.mHasSizeCompat = true; + doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo); assertTrue(mWindowManager.createLayout(/* canShow= */ false)); verify(mWindowManager, never()).inflateLayout(); @@ -199,6 +208,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { // No diff clearInvocations(mWindowManager); TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN); + doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any()); assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true)); verify(mWindowManager, never()).updateSurfacePosition(); @@ -284,6 +294,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testUpdateCompatInfoLayoutNotInflatedYet() { mWindowManager.mHasSizeCompat = true; + doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any()); mWindowManager.createLayout(/* canShow= */ false); verify(mWindowManager, never()).inflateLayout(); @@ -353,6 +364,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase { // Create button if it is not created. mWindowManager.mLayout = null; mWindowManager.mHasSizeCompat = true; + doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo); mWindowManager.updateVisibility(/* canShow= */ true); verify(mWindowManager).createLayout(/* canShow= */ true); @@ -464,6 +476,37 @@ public class CompatUIWindowManagerTest extends ShellTestCase { Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); } + @Test + public void testShouldShowSizeCompatRestartButton() { + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON); + + doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance(); + mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, + mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCompatUIConfiguration, mOnRestartButtonClicked); + + // Simulate rotation of activity in square display + TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000)); + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000; + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850; + + assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); + + // Simulate exiting split screen/folding + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000; + assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); + + // Simulate folding + taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000)); + assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); + + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000; + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500; + assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); + } + private static TaskInfo createTaskInfo(boolean hasSizeCompat, @AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) { ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 3bc90ade898e..be639e867e0b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -34,6 +34,7 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.kotlin.mock import org.mockito.kotlin.never +import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.verifyZeroInteractions import org.mockito.kotlin.whenever @@ -150,6 +151,23 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { } @Test + fun startDragToDesktop_anotherTransitionInProgress_startDropped() { + val task = createTask() + val dragAnimator = mock<MoveToDesktopAnimator>() + + // Simulate attempt to start two drag to desktop transitions. + startDragToDesktopTransition(task, dragAnimator) + startDragToDesktopTransition(task, dragAnimator) + + // Verify transition only started once. + verify(transitions, times(1)).startTransition( + eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP), + any(), + eq(handler) + ) + } + + @Test fun cancelDragToDesktop_startWasReady_cancel() { val task = createTask() val dragAnimator = mock<MoveToDesktopAnimator>() @@ -189,6 +207,32 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { verifyZeroInteractions(dragAnimator) } + @Test + fun cancelDragToDesktop_transitionNotInProgress_dropCancel() { + // Then cancel is called before the transition was started. + handler.cancelDragToDesktopTransition() + + // Verify cancel is dropped. + verify(transitions, never()).startTransition( + eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), + any(), + eq(handler) + ) + } + + @Test + fun finishDragToDesktop_transitionNotInProgress_dropFinish() { + // Then finish is called before the transition was started. + handler.finishDragToDesktopTransition(WindowContainerTransaction()) + + // Verify finish is dropped. + verify(transitions, never()).startTransition( + eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP), + any(), + eq(handler) + ) + } + private fun startDragToDesktopTransition( task: RunningTaskInfo, dragAnimator: MoveToDesktopAnimator diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 08412101c30c..86253f35a51d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -144,13 +144,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { } @Test - fun testDragResize_noMove_showsResizeVeil() { + fun testDragResize_noMove_doesNotShowResizeVeil() { taskPositioner.onDragPositioningStart( CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) + verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningEnd( STARTING_BOUNDS.left.toFloat(), @@ -162,7 +162,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}}, eq(taskPositioner)) - verify(mockDesktopWindowDecoration).hideResizeVeil() + verify(mockDesktopWindowDecoration, never()).hideResizeVeil() } @Test @@ -212,7 +212,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningMove( STARTING_BOUNDS.right.toFloat() + 10, @@ -222,6 +221,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { val rectAfterMove = Rect(STARTING_BOUNDS) rectAfterMove.right += 10 rectAfterMove.top += 10 + verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove) verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -237,7 +237,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { val rectAfterEnd = Rect(rectAfterMove) rectAfterEnd.right += 10 rectAfterEnd.top += 10 - verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any()) + verify(mockDesktopWindowDecoration).updateResizeVeil(any()) verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct -> return@argThat wct.changes.any { (token, change) -> token == taskBinder && @@ -253,7 +253,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) - verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS) taskPositioner.onDragPositioningMove( STARTING_BOUNDS.left.toFloat(), diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index c9d5e074271b..d9166a16cdea 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <cstddef> #include <limits> +#include <optional> #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -50,7 +51,9 @@ namespace { // contiguous block of memory to store both the TypeSpec struct and // the Type structs. struct TypeSpecBuilder { - explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {} + explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) { + type_entries.reserve(dtohs(header_->typesCount)); + } void AddType(incfs::verified_map_ptr<ResTable_type> type) { TypeSpec::TypeEntry& entry = type_entries.emplace_back(); @@ -59,6 +62,7 @@ struct TypeSpecBuilder { } TypeSpec Build() { + type_entries.shrink_to_fit(); return {header_, std::move(type_entries)}; } @@ -450,7 +454,8 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, package_property_t property_flags) { ATRACE_NAME("LoadedPackage::Load"); - std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage()); + const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0; + std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups)); // typeIdOffset was added at some point, but we still must recognize apps built before this // was added. @@ -499,7 +504,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // A map of TypeSpec builders, each associated with an type index. // We use these to accumulate the set of Types available for a TypeSpec, and later build a single, // contiguous block of memory that holds all the Types together with the TypeSpec. - std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map; + std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map; ChunkIterator iter(chunk.data_ptr(), chunk.data_size()); while (iter.HasNext()) { @@ -567,14 +572,14 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, return {}; } - if (entry_count * sizeof(uint32_t) > chunk.data_size()) { + if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) { LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries."; return {}; } - std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id]; - if (builder_ptr == nullptr) { - builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified()); + auto& maybe_type_builder = type_builder_map[type_spec->id]; + if (!maybe_type_builder) { + maybe_type_builder.emplace(type_spec.verified()); loaded_package->resource_ids_.set(type_spec->id, entry_count); } else { LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", @@ -594,9 +599,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } // Type chunks must be preceded by their TypeSpec chunks. - std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id]; - if (builder_ptr != nullptr) { - builder_ptr->AddType(type.verified()); + auto& maybe_type_builder = type_builder_map[type->id]; + if (maybe_type_builder) { + maybe_type_builder->AddType(type.verified()); } else { LOG(ERROR) << StringPrintf( "RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.", diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 4c992becda7c..2c99f1aa3675 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -447,15 +447,19 @@ Res_png_9patch* Res_png_9patch::deserialize(void* inData) // -------------------------------------------------------------------- // -------------------------------------------------------------------- -ResStringPool::ResStringPool() - : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) -{ +ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) { } -ResStringPool::ResStringPool(const void* data, size_t size, bool copyData) - : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) -{ - setTo(data, size, copyData); +ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() { + if (optimize_name_lookups) { + mIndexLookupCache.emplace(); + } +} + +ResStringPool::ResStringPool(const void* data, size_t size, bool copyData, + bool optimize_name_lookups) + : ResStringPool(optimize_name_lookups) { + setTo(data, size, copyData); } ResStringPool::~ResStringPool() @@ -683,6 +687,14 @@ status_t ResStringPool::setTo(incfs::map_ptr<void> data, size_t size, bool copyD mStylePoolSize = 0; } + if (mIndexLookupCache) { + if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) { + mIndexLookupCache->first.reserve(mHeader->stringCount); + } else { + mIndexLookupCache->second.reserve(mHeader->stringCount); + } + } + return (mError=NO_ERROR); } @@ -708,6 +720,10 @@ void ResStringPool::uninit() free(mOwnedData); mOwnedData = NULL; } + if (mIndexLookupCache) { + mIndexLookupCache->first.clear(); + mIndexLookupCache->second.clear(); + } } /** @@ -824,11 +840,11 @@ base::expected<StringPiece16, NullOrIOError> ResStringPool::stringAt(size_t idx) // encLen must be less than 0x7FFF due to encoding. if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) { - AutoMutex lock(mDecodeLock); + AutoMutex lock(mCachesLock); - if (mCache != NULL && mCache[idx] != NULL) { - return StringPiece16(mCache[idx], *u16len); - } + if (mCache != NULL && mCache[idx] != NULL) { + return StringPiece16(mCache[idx], *u16len); + } // Retrieve the actual length of the utf8 string if the // encoded length was truncated @@ -1093,12 +1109,24 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ // block, start searching at the back. String8 str8(str, strLen); const size_t str8Len = str8.size(); + std::optional<AutoMutex> cacheLock; + if (mIndexLookupCache) { + cacheLock.emplace(mCachesLock); + if (auto it = mIndexLookupCache->first.find(std::string_view(str8)); + it != mIndexLookupCache->first.end()) { + return it->second; + } + } + for (int i=mHeader->stringCount-1; i>=0; i--) { const base::expected<StringPiece, NullOrIOError> s = string8At(i); if (UNLIKELY(IsIOError(s))) { return base::unexpected(s.error()); } if (s.has_value()) { + if (mIndexLookupCache) { + mIndexLookupCache->first.insert({*s, i}); + } if (kDebugStringPoolNoisy) { ALOGI("Looking at %s, i=%d\n", s->data(), i); } @@ -1151,20 +1179,32 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_ // most often this happens because we want to get IDs for style // span tags; since those always appear at the end of the string // block, start searching at the back. + std::optional<AutoMutex> cacheLock; + if (mIndexLookupCache) { + cacheLock.emplace(mCachesLock); + if (auto it = mIndexLookupCache->second.find({str, strLen}); + it != mIndexLookupCache->second.end()) { + return it->second; + } + } for (int i=mHeader->stringCount-1; i>=0; i--) { const base::expected<StringPiece16, NullOrIOError> s = stringAt(i); if (UNLIKELY(IsIOError(s))) { return base::unexpected(s.error()); } if (kDebugStringPoolNoisy) { - ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i); + ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i); } - if (s.has_value() && strLen == s->size() && - strzcmp16(s->data(), s->size(), str, strLen) == 0) { + if (s.has_value()) { + if (mIndexLookupCache) { + mIndexLookupCache->second.insert({*s, i}); + } + if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) { if (kDebugStringPoolNoisy) { - ALOGI("MATCH!"); + ALOGI("MATCH16!"); } return i; + } } } } diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index 60689128dffb..d9f7c2a1ac19 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -71,7 +71,7 @@ class OverlayDynamicRefTable : public DynamicRefTable { // Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target // resource. - virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const; + status_t lookupResourceIdNoRewrite(uint32_t* resId) const; const Idmap_data_header* data_header_; const Idmap_overlay_entry* entries_; diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 3a7287187781..413b27829474 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -99,6 +99,9 @@ enum : package_property_t { // The apk assets only contain the overlayable declarations information. PROPERTY_ONLY_OVERLAYABLES = 1U << 5U, + + // Optimize the resource lookups by name via an in-memory lookup table. + PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U, }; struct OverlayableInfo { @@ -285,7 +288,9 @@ class LoadedPackage { private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); - LoadedPackage() = default; + explicit LoadedPackage(bool optimize_name_lookups = false) + : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) { + } ResStringPool type_string_pool_; ResStringPool key_string_pool_; diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index c0514fdff469..3d1403d0e039 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -22,27 +22,28 @@ #include <android-base/expected.h> #include <android-base/unique_fd.h> - +#include <android/configuration.h> #include <androidfw/Asset.h> #include <androidfw/Errors.h> #include <androidfw/LocaleData.h> #include <androidfw/StringPiece.h> #include <utils/ByteOrder.h> #include <utils/Errors.h> +#include <utils/KeyedVector.h> #include <utils/String16.h> #include <utils/Vector.h> -#include <utils/KeyedVector.h> - #include <utils/threads.h> #include <stdint.h> #include <sys/types.h> -#include <android/configuration.h> - #include <array> #include <map> #include <memory> +#include <optional> +#include <string_view> +#include <unordered_map> +#include <utility> namespace android { @@ -512,23 +513,24 @@ struct ResStringPool_span class ResStringPool { public: - ResStringPool(); - ResStringPool(const void* data, size_t size, bool copyData=false); - virtual ~ResStringPool(); + ResStringPool(); + explicit ResStringPool(bool optimize_name_lookups); + ResStringPool(const void* data, size_t size, bool copyData = false, + bool optimize_name_lookups = false); + virtual ~ResStringPool(); - void setToEmpty(); - status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false); + void setToEmpty(); + status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false); - status_t getError() const; + status_t getError() const; - void uninit(); + void uninit(); - // Return string entry as UTF16; if the pool is UTF8, the string will - // be converted before returning. - inline base::expected<StringPiece16, NullOrIOError> stringAt( - const ResStringPool_ref& ref) const { - return stringAt(ref.index); - } + // Return string entry as UTF16; if the pool is UTF8, the string will + // be converted before returning. + inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const { + return stringAt(ref.index); + } virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const; // Note: returns null if the string pool is not UTF8. @@ -557,7 +559,7 @@ private: void* mOwnedData; incfs::verified_map_ptr<ResStringPool_header> mHeader; size_t mSize; - mutable Mutex mDecodeLock; + mutable Mutex mCachesLock; incfs::map_ptr<uint32_t> mEntries; incfs::map_ptr<uint32_t> mEntryStyles; incfs::map_ptr<void> mStrings; @@ -566,6 +568,10 @@ private: incfs::map_ptr<uint32_t> mStyles; uint32_t mStylePoolSize; // number of uint32_t + mutable std::optional<std::pair<std::unordered_map<std::string_view, int>, + std::unordered_map<std::u16string_view, int>>> + mIndexLookupCache; + base::expected<StringPiece, NullOrIOError> stringDecodeAt( size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const; }; @@ -1401,8 +1407,8 @@ struct ResTable_typeSpec // Must be 0. uint8_t res0; - // Must be 0. - uint16_t res1; + // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec. + uint16_t typesCount; // Number of uint32_t entry configuration masks that follow. uint32_t entryCount; diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index 00d049cde925..6ebfc63bdf26 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -41,6 +41,14 @@ inline bool deprecate_ui_fonts() { #endif // __ANDROID__ } +inline bool inter_character_justification() { +#ifdef __ANDROID__ + return com_android_text_flags_inter_character_justification(); +#else + return true; +#endif // __ANDROID__ +} + } // namespace text_feature } // namespace android diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index f4ee36ec66d1..bbb142014ed8 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -131,11 +131,6 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( const std::vector<minikin::FontVariation>& variations) const { SkFontArguments args; - int ttcIndex; - std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex)); - LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed"); - - args.setCollectionIndex(ttcIndex); std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation; skVariation.resize(variations.size()); for (size_t i = 0; i < variations.size(); i++) { @@ -143,11 +138,10 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( skVariation[i].value = SkFloatToScalar(variations[i].value); } args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())}); - sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr(); - sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args)); + sk_sp<SkTypeface> face = mTypeface->makeClone(args); return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize, - mFilePath, ttcIndex, variations); + mFilePath, mTtcIndex, variations); } // hinting<<16 | edging<<8 | bools:5bits diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 833069f363c8..56133699d5f5 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -72,10 +72,13 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla const minikin::Range contextRange(contextStart, contextStart + contextCount); const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); + const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification() + ? paint->getRunFlag() + : minikin::RunFlag::NONE; if (mt == nullptr) { return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags, - minikinPaint, startHyphen, endHyphen); + minikinPaint, startHyphen, endHyphen, minikinRunFlag); } else { return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen); } @@ -102,9 +105,12 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const minikin::Range range(start, start + count); const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit(); const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit(); + const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification() + ? paint->getRunFlag() + : minikin::RunFlag::NONE; return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, - endHyphen, advances, bounds, clusterCount); + endHyphen, advances, bounds, clusterCount, minikinRunFlag); } minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags, diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index ef4dce57bf46..708f96e5a070 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -25,6 +25,7 @@ #include <minikin/FontFamily.h> #include <minikin/FontFeature.h> #include <minikin/Hyphenator.h> +#include <minikin/Layout.h> #include <string> @@ -144,6 +145,9 @@ public: bool isDevKern() const { return mDevKern; } void setDevKern(bool d) { mDevKern = d; } + minikin::RunFlag getRunFlag() const { return mRunFlag; } + void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; } + // Deprecated -- bitmapshaders will be taking this flag explicitly bool isFilterBitmap() const { return mFilterBitmap; } void setFilterBitmap(bool filter) { mFilterBitmap = filter; } @@ -188,6 +192,7 @@ private: bool mStrikeThru = false; bool mUnderline = false; bool mDevKern = false; + minikin::RunFlag mRunFlag = minikin::RunFlag::NONE; }; } // namespace android diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index aac928f85924..c32ea01db7b7 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -47,8 +47,8 @@ Paint::Paint(const Paint& paint) , mFilterBitmap(paint.mFilterBitmap) , mStrikeThru(paint.mStrikeThru) , mUnderline(paint.mUnderline) - , mDevKern(paint.mDevKern) {} - + , mDevKern(paint.mDevKern) + , mRunFlag(paint.mRunFlag) {} Paint::~Paint() {} @@ -68,21 +68,19 @@ Paint& Paint::operator=(const Paint& other) { mStrikeThru = other.mStrikeThru; mUnderline = other.mUnderline; mDevKern = other.mDevKern; + mRunFlag = other.mRunFlag; return *this; } bool operator==(const Paint& a, const Paint& b) { - return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && - a.mFont == b.mFont && - a.mLooper == b.mLooper && - a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing && - a.mFontFeatureSettings == b.mFontFeatureSettings && + return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont && + a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing && + a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && a.mTypeface == b.mTypeface && a.mAlign == b.mAlign && - a.mFilterBitmap == b.mFilterBitmap && - a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline && - a.mDevKern == b.mDevKern; + a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru && + a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag; } void Paint::reset() { @@ -96,6 +94,7 @@ void Paint::reset() { mStrikeThru = false; mUnderline = false; mDevKern = false; + mRunFlag = minikin::RunFlag::NONE; } void Paint::setLooper(sk_sp<BlurDrawLooper> looper) { @@ -133,6 +132,8 @@ static const uint32_t sForceAutoHinting = 0x800; // flags related to minikin::Paint static const uint32_t sUnderlineFlag = 0x08; static const uint32_t sStrikeThruFlag = 0x10; +static const uint32_t sTextRunLeftEdge = 0x2000; +static const uint32_t sTextRunRightEdge = 0x4000; // flags no longer supported on native side (but mirrored for compatibility) static const uint32_t sDevKernFlag = 0x100; @@ -186,6 +187,12 @@ uint32_t Paint::getJavaFlags() const { flags |= -(int)mUnderline & sUnderlineFlag; flags |= -(int)mDevKern & sDevKernFlag; flags |= -(int)mFilterBitmap & sFilterBitmapFlag; + if (mRunFlag & minikin::RunFlag::LEFT_EDGE) { + flags |= sTextRunLeftEdge; + } + if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) { + flags |= sTextRunRightEdge; + } return flags; } @@ -196,6 +203,15 @@ void Paint::setJavaFlags(uint32_t flags) { mUnderline = (flags & sUnderlineFlag) != 0; mDevKern = (flags & sDevKernFlag) != 0; mFilterBitmap = (flags & sFilterBitmapFlag) != 0; + + std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE; + if (flags & sTextRunLeftEdge) { + rawFlag |= minikin::RunFlag::LEFT_EDGE; + } + if (flags & sTextRunRightEdge) { + rawFlag |= minikin::RunFlag::RIGHT_EDGE; + } + mRunFlag = static_cast<minikin::RunFlag>(rawFlag); } } // namespace android diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 8ba750372d18..d5725935551a 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -613,6 +613,12 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); ScopedCharArrayRO text(env, charArray); + + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + // drawTextString and drawTextChars doesn't use context info get_canvas(canvasHandle)->drawText( text.get() + index, count, // text buffer @@ -620,6 +626,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c 0, count, // context range x, y, // draw position static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); + paint->setRunFlag(originalRunFlag); } static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj, @@ -629,6 +636,12 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); const int count = end - start; + + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + // drawTextString and drawTextChars doesn't use context info get_canvas(canvasHandle)->drawText( text.get() + start, count, // text buffer @@ -636,6 +649,7 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str 0, count, // context range x, y, // draw position static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */); + paint->setRunFlag(originalRunFlag); } static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray, @@ -681,9 +695,15 @@ static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharA jchar* jchars = env->GetCharArrayElements(text, NULL); + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface); + paint->setRunFlag(originalRunFlag); env->ReleaseCharArrayElements(text, jchars, 0); } @@ -697,9 +717,15 @@ static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstri const jchar* jchars = env->GetStringChars(text, NULL); int count = env->GetStringLength(text); + // The drawText API is designed to draw entire line, so ignore the text run flag and draw the + // text as entire line mode. + const minikin::RunFlag originalRunFlag = paint->getRunFlag(); + paint->setRunFlag(minikin::RunFlag::WHOLE_LINE); + get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface); + paint->setRunFlag(originalRunFlag); env->ReleaseStringChars(text, jchars); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index c5ffbb70213e..c8d598702a7c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -118,7 +118,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) { + const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) { if (!isCapturingSkp() && !mHardwareBuffer) { mEglManager.damageFrame(frame, dirty); } @@ -171,6 +171,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || ProfileType::None != Properties::getProfileType())) { + std::scoped_lock lock(profilerLock); SkCanvas* profileCanvas = surface->getCanvas(); SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height()); profiler->draw(profileRenderer); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 098a74655831..ebe8b6e15d44 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -42,7 +42,8 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams) override; + const renderthread::HardwareBufferRenderParams& bufferParams, + std::mutex& profilerLock) override; GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, const SkRect& screenDirty, FrameInfo* currentFrameInfo, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index d74748936d15..fd0a8e06f39c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -75,7 +75,7 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) { + const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) { sk_sp<SkSurface> backBuffer; SkMatrix preTransform; if (mHardwareBuffer) { @@ -103,6 +103,7 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw( // Draw visual debugging features if (CC_UNLIKELY(Properties::showDirtyRegions || ProfileType::None != Properties::getProfileType())) { + std::scoped_lock lock(profilerLock); SkCanvas* profileCanvas = backBuffer->getCanvas(); SkAutoCanvasRestore saver(profileCanvas, true); profileCanvas->concat(preTransform); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index e2ea57d32cd5..624eaa51a584 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -42,7 +42,8 @@ public: const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler, - const renderthread::HardwareBufferRenderParams& bufferParams) override; + const renderthread::HardwareBufferRenderParams& bufferParams, + std::mutex& profilerLock) override; GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; } bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult, const SkRect& screenDirty, FrameInfo* currentFrameInfo, diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index eb4d4948dc53..ac2a9366a1f6 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -124,24 +124,19 @@ void CacheManager::trimMemory(TrimLevel mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(GrSyncCpu::kYes); - switch (mode) { - case TrimLevel::BACKGROUND: - mGrContext->freeGpuResources(); - SkGraphics::PurgeAllCaches(); - mRenderThread.destroyRenderingContext(); - break; - case TrimLevel::UI_HIDDEN: - // Here we purge all the unlocked scratch resources and then toggle the resources cache - // limits between the background and max amounts. This causes the unlocked resources - // that have persistent data to be purged in LRU order. - mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); - SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); - mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); - mGrContext->setResourceCacheLimit(mMaxResourceBytes); - SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); - break; - default: - break; + if (mode >= TrimLevel::BACKGROUND) { + mGrContext->freeGpuResources(); + SkGraphics::PurgeAllCaches(); + mRenderThread.destroyRenderingContext(); + } else if (mode == TrimLevel::UI_HIDDEN) { + // Here we purge all the unlocked scratch resources and then toggle the resources cache + // limits between the background and max amounts. This causes the unlocked resources + // that have persistent data to be purged in LRU order. + mGrContext->setResourceCacheLimit(mBackgroundResourceBytes); + SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes); + mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly)); + mGrContext->setResourceCacheLimit(mMaxResourceBytes); + SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes); } } diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 56b52dc2abe7..9c7f7cc24266 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -625,14 +625,9 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { { // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw // or it can lead to memory corruption. - // This lock is overly broad, but it's the quickest fix since this mutex is otherwise - // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is - // the thread we're primarily concerned about being responsive, this being too broad - // shouldn't pose a performance issue. - std::scoped_lock lock(mFrameMetricsReporterMutex); - drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, - &mLayerUpdateQueue, mContentDrawBounds, mOpaque, - mLightInfo, mRenderNodes, &(profiler()), mBufferParams); + drawResult = mRenderPipeline->draw( + frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds, + mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock()); } uint64_t frameCompleteNr = getFrameNumber(); @@ -762,7 +757,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { mCurrentFrameInfo->markFrameCompleted(); mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted) = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted); - std::scoped_lock lock(mFrameMetricsReporterMutex); + std::scoped_lock lock(mFrameInfoMutex); mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr, mSurfaceControlGenerationId); } @@ -791,7 +786,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) { void CanvasContext::reportMetricsWithPresentTime() { { // acquire lock - std::scoped_lock lock(mFrameMetricsReporterMutex); + std::scoped_lock lock(mFrameInfoMutex); if (mFrameMetricsReporter == nullptr) { return; } @@ -826,7 +821,7 @@ void CanvasContext::reportMetricsWithPresentTime() { forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime; { // acquire lock - std::scoped_lock lock(mFrameMetricsReporterMutex); + std::scoped_lock lock(mFrameInfoMutex); if (mFrameMetricsReporter != nullptr) { mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/, frameNumber, surfaceControlId); @@ -835,7 +830,7 @@ void CanvasContext::reportMetricsWithPresentTime() { } void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) { - std::scoped_lock lock(mFrameMetricsReporterMutex); + std::scoped_lock lock(mFrameInfoMutex); if (mFrameMetricsReporter.get() == nullptr) { mFrameMetricsReporter.reset(new FrameMetricsReporter()); } @@ -849,7 +844,7 @@ void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) { } void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) { - std::scoped_lock lock(mFrameMetricsReporterMutex); + std::scoped_lock lock(mFrameInfoMutex); if (mFrameMetricsReporter.get() != nullptr) { mFrameMetricsReporter->removeObserver(observer); if (!mFrameMetricsReporter->hasObservers()) { @@ -886,7 +881,7 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId); if (frameInfo != nullptr) { - std::scoped_lock lock(instance->mFrameMetricsReporterMutex); + std::scoped_lock lock(instance->mFrameInfoMutex); frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime, frameInfo->get(FrameInfoIndex::SwapBuffersCompleted)); frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max( diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index be9b649030ae..e2e3fa35b9b0 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -164,6 +164,7 @@ public: void notifyFramePending(); FrameInfoVisualizer& profiler() { return mProfiler; } + std::mutex& profilerLock() { return mFrameInfoMutex; } void dumpFrames(int fd); void resetFrameStats(); @@ -342,9 +343,8 @@ private: std::string mName; JankTracker mJankTracker; FrameInfoVisualizer mProfiler; - std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter - GUARDED_BY(mFrameMetricsReporterMutex); - std::mutex mFrameMetricsReporterMutex; + std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex); + std::mutex mFrameInfoMutex; std::set<RenderNode*> mPrefetchedLayers; diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 9c879d5a58d1..b8c3a4de2bd4 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -68,7 +68,8 @@ public: const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler, - const HardwareBufferRenderParams& bufferParams) = 0; + const HardwareBufferRenderParams& bufferParams, + std::mutex& profilerLock) = 0; virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index b002bbf20c08..ea136edf46be 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1079,7 +1079,7 @@ public final class AudioFormat implements Parcelable { * @return one of the values that can be set in {@link Builder#setEncoding(int)} or * {@link AudioFormat#ENCODING_INVALID} if not set. */ - public int getEncoding() { + public @Encoding int getEncoding() { return mEncoding; } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 9616b5d44540..62412df97043 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -734,7 +734,7 @@ public final class MediaRouter2 { * request. * @param transferInitiatorPackageName the package name of the app that initiated the transfer. * This value is used with the user handle to populate {@link - * RoutingController#wasTransferRequestedBySelf()}. + * RoutingController#wasTransferInitiatedBySelf()}. * @hide */ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) @@ -1550,11 +1550,11 @@ public final class MediaRouter2 { } /** - * Returns whether the transfer was requested by the calling app (as determined by comparing + * Returns whether the transfer was initiated by the calling app (as determined by comparing * {@link UserHandle} and package name). */ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) - public boolean wasTransferRequestedBySelf() { + public boolean wasTransferInitiatedBySelf() { RoutingSessionInfo sessionInfo = getRoutingSessionInfo(); UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index a8ffd2b4dd8f..2f6575e65077 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -149,4 +149,7 @@ interface ITvInputManager { // For CTS purpose only. Add/remove a TvInputHardware device void addHardwareDevice(in int deviceId); void removeHardwareDevice(in int deviceId); + + // For freezing video playback + void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId); } diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index e37ee6e342e5..a93f18db3dca 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -83,4 +83,7 @@ oneway interface ITvInputSession { // For TV messages void notifyTvMessage(int type, in Bundle data); void setTvMessageEnabled(int type, boolean enabled); + + // For freezing video + void setVideoFrozen(boolean isFrozen); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index ae3ee6535c71..921104dc70c8 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -81,6 +81,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_NOTIFY_TV_MESSAGE = 32; private static final int DO_STOP_PLAYBACK = 33; private static final int DO_START_PLAYBACK = 34; + private static final int DO_SET_VIDEO_FROZEN = 35; private final boolean mIsRecordingSession; private final HandlerCaller mCaller; @@ -296,6 +297,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.startPlayback(); break; } + case DO_SET_VIDEO_FROZEN: { + mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -483,6 +488,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override + public void setVideoFrozen(boolean isFrozen) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen)); + } + + @Override public void notifyTvMessage(int type, Bundle data) { mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data)); } diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index c685a5adb08b..51b2542688a6 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -3334,6 +3334,18 @@ public final class TvInputManager { } } + void setVideoFrozen(boolean isFrozen) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.setVideoFrozen(mToken, isFrozen, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Sends TV messages to the service for testing purposes */ diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 55fa51755177..76d8e50f94be 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1558,6 +1558,17 @@ public abstract class TvInputService extends Service { } /** + * Called when a request to freeze the video is received from the TV app. The audio should + * continue playback while the video is frozen. + * + * <p> This should freeze the video to the last frame when the state is set to {@code true}. + * @param isFrozen whether or not the video should be frozen. + * @hide + */ + public void onSetVideoFrozen(boolean isFrozen) { + } + + /** * Called when the application requests to play a given recorded TV program. * * @param recordedProgramUri The URI of a recorded TV program. @@ -2034,6 +2045,13 @@ public abstract class TvInputService extends Service { } /** + * Calls {@link #onSetVideoFrozen(boolean)}. + */ + void setVideoFrozen(boolean isFrozen) { + onSetVideoFrozen(isFrozen); + } + + /** * Calls {@link #onTimeShiftPlay(Uri)}. */ void timeShiftPlay(Uri recordedProgramUri) { diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 233f96675543..cb45661a01ac 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -675,6 +675,23 @@ public class TvView extends ViewGroup { } /** + * Sets whether or not the video is frozen. While the video is frozen, audio playback will + * continue. + * + * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is + * received with the command to freeze the video. + * + * <p> This will freeze the video to the last frame when the state is set to {@code true}. + * @param isFrozen whether or not the video is frozen. + * @hide + */ + public void setVideoFrozen(boolean isFrozen) { + if (mSession != null) { + mSession.setVideoFrozen(isFrozen); + } + } + + /** * Sends TV messages to the session for testing purposes * * @hide diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 5cc86bacf54f..79364034ac2a 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -112,7 +112,8 @@ public abstract class TvInteractiveAppService extends Service { PLAYBACK_COMMAND_TYPE_TUNE_PREV, PLAYBACK_COMMAND_TYPE_STOP, PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME, - PLAYBACK_COMMAND_TYPE_SELECT_TRACK + PLAYBACK_COMMAND_TYPE_SELECT_TRACK, + PLAYBACK_COMMAND_TYPE_FREEZE }) public @interface PlaybackCommandType {} @@ -142,8 +143,11 @@ public abstract class TvInteractiveAppService extends Service { * Playback command type: select the given track. */ public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track"; - - + /** + * Playback command type: freeze the video playback on the current frame. + * @hide + */ + public static final String PLAYBACK_COMMAND_TYPE_FREEZE = "freeze"; /** @hide */ @Retention(RetentionPolicy.SOURCE) diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java index f9eaabd800b1..0b9429debb62 100644 --- a/media/java/android/media/tv/tuner/Lnb.java +++ b/media/java/android/media/tv/tuner/Lnb.java @@ -264,6 +264,25 @@ public class Lnb implements AutoCloseable { } } + /* package */ void closeInternal() { + synchronized (mLock) { + if (mIsClosed) { + return; + } + int res = nativeClose(); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "Failed to close LNB"); + } else { + mIsClosed = true; + if (mOwner != null) { + mOwner.releaseLnb(); + mOwner = null; + } + mCallbackMap.clear(); + } + } + } + /** * Sets the LNB's power voltage. * @@ -330,22 +349,7 @@ public class Lnb implements AutoCloseable { public void close() { acquireTRMSLock("close()"); try { - synchronized (mLock) { - if (mIsClosed) { - return; - } - int res = nativeClose(); - if (res != Tuner.RESULT_SUCCESS) { - TunerUtils.throwExceptionForResult(res, "Failed to close LNB"); - } else { - mIsClosed = true; - if (mOwner != null) { - mOwner.releaseLnb(); - mOwner = null; - } - mCallbackMap.clear(); - } - } + closeInternal(); } finally { releaseTRMSLock(); } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 09f09b94ac0d..f28c2c17167f 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.hardware.tv.tuner.Constant; import android.hardware.tv.tuner.Constant64Bit; import android.hardware.tv.tuner.FrontendScanType; +import android.media.MediaCodec; import android.media.tv.TvInputService; import android.media.tv.tuner.dvr.DvrPlayback; import android.media.tv.tuner.dvr.DvrRecorder; @@ -272,8 +273,12 @@ public class Tuner implements AutoCloseable { try { System.loadLibrary("media_tv_tuner"); nativeInit(); + // Load and initialize MediaCodec to avoid flaky cts test result. + Class.forName(MediaCodec.class.getName()); } catch (UnsatisfiedLinkError e) { Log.d(TAG, "tuner JNI library not found!"); + } catch (ClassNotFoundException e) { + Log.e(TAG, "MediaCodec class not found!", e); } } @@ -914,7 +919,7 @@ public class Tuner implements AutoCloseable { if (DEBUG) { Log.d(TAG, "calling mLnb.close() : " + mClientId); } - mLnb.close(); + mLnb.closeInternal(); } else { if (DEBUG) { Log.d(TAG, "NOT calling mLnb.close() : " + mClientId); @@ -2348,6 +2353,7 @@ public class Tuner implements AutoCloseable { @Nullable public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) { + acquireTRMSLock("openLnbByName"); mLnbLock.lock(); try { Objects.requireNonNull(name, "LNB name must not be null"); @@ -2356,7 +2362,7 @@ public class Tuner implements AutoCloseable { Lnb newLnb = nativeOpenLnbByName(name); if (newLnb != null) { if (mLnb != null) { - mLnb.close(); + mLnb.closeInternal(); mLnbHandle = null; } mLnb = newLnb; @@ -2367,6 +2373,7 @@ public class Tuner implements AutoCloseable { } return mLnb; } finally { + releaseTRMSLock(); mLnbLock.unlock(); } } @@ -2784,8 +2791,8 @@ public class Tuner implements AutoCloseable { } } + // Must be called while TRMS lock is being held /* package */ void releaseLnb() { - acquireTRMSLock("releaseLnb()"); mLnbLock.lock(); try { if (mLnbHandle != null) { @@ -2802,7 +2809,6 @@ public class Tuner implements AutoCloseable { } mLnb = null; } finally { - releaseTRMSLock(); mLnbLock.unlock(); } } diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 24c145f890c4..7573474f65b4 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -204,6 +204,7 @@ package android.nfc.cardemulation { method public boolean isDefaultServiceForAid(android.content.ComponentName, String); method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean removeAidsForService(android.content.ComponentName, String); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 286cf2890eea..bec62c5b1b82 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -97,4 +97,7 @@ interface INfcAdapter WlcLDeviceInfo getWlcLDeviceInfo(); void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags); + + void notifyPollingLoop(in Bundle frame); + void notifyHceDeactivated(); } diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index f4b46046bc3e..791bd8c9e6f4 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -32,6 +32,7 @@ interface INfcCardEmulation boolean setDefaultForNextTap(int userHandle, in ComponentName service); boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); + boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index 8219d2f873de..68c16e69f3af 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -26,6 +26,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.Activity; import android.app.PendingIntent; @@ -2752,6 +2753,64 @@ public final class NfcAdapter { } } + /** + * Notifies the system of a new polling loop. + * + * @param frame is the new frame. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void notifyPollingLoop(@NonNull Bundle frame) { + try { + if (sService == null) { + attemptDeadServiceRecovery(null); + } + sService.notifyPollingLoop(frame); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return; + } + try { + sService.notifyPollingLoop(frame); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + } + } + + /** + * Notifies the system of a an HCE session being deactivated. + * * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void notifyHceDeactivated() { + try { + if (sService == null) { + attemptDeadServiceRecovery(null); + } + sService.notifyHceDeactivated(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return; + } + try { + sService.notifyHceDeactivated(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + } + } + /** * Sets NFC charging feature. * <p>This API is for the Settings application. @@ -2767,6 +2826,7 @@ public final class NfcAdapter { } try { return sService.enableWlc(enable); + } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java index 41dee3ab035c..426c5aac487b 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -52,6 +52,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -102,6 +103,8 @@ public final class ApduServiceInfo implements Parcelable { */ private final HashMap<String, AidGroup> mDynamicAidGroups; + private final ArrayList<String> mPollingLoopFilters; + /** * Whether this service should only be started when the device is unlocked. */ @@ -169,6 +172,7 @@ public final class ApduServiceInfo implements Parcelable { this.mDescription = description; this.mStaticAidGroups = new HashMap<String, AidGroup>(); this.mDynamicAidGroups = new HashMap<String, AidGroup>(); + this.mPollingLoopFilters = new ArrayList<String>(); this.mOffHostName = offHost; this.mStaticOffHostName = staticOffHost; this.mOnHost = onHost; @@ -282,6 +286,7 @@ public final class ApduServiceInfo implements Parcelable { mStaticAidGroups = new HashMap<String, AidGroup>(); mDynamicAidGroups = new HashMap<String, AidGroup>(); + mPollingLoopFilters = new ArrayList<String>(); mOnHost = onHost; final int depth = parser.getDepth(); @@ -364,6 +369,15 @@ public final class ApduServiceInfo implements Parcelable { Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); } a.recycle(); + } else if (eventType == XmlPullParser.START_TAG + && "polling-loop-filter".equals(tagName) && currentGroup == null) { + final TypedArray a = res.obtainAttributes(attrs, + com.android.internal.R.styleable.PollingLoopFilter); + String plf = + a.getString(com.android.internal.R.styleable.PollingLoopFilter_name) + .toUpperCase(Locale.ROOT); + mPollingLoopFilters.add(plf); + a.recycle(); } } } catch (NameNotFoundException e) { @@ -420,6 +434,16 @@ public final class ApduServiceInfo implements Parcelable { } /** + * Returns the current polling loop filters for this service. + * @return List of polling loop filters. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + @NonNull + public List<String> getPollingLoopFilters() { + return mPollingLoopFilters; + } + + /** * Returns a consolidated list of AIDs with prefixes from the AID groups * registered by this service. Note that if a service has both * a static (manifest-based) AID group for a category and a dynamic @@ -596,6 +620,26 @@ public final class ApduServiceInfo implements Parcelable { } /** + * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be + * delivered to {@link HostApduService#processPollingFrames(List)}. + * @param pollingLoopFilter this polling loop filter to add. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void addPollingLoopFilter(@NonNull String pollingLoopFilter) { + mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT)); + } + + /** + * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no + * longer be delivered to {@link HostApduService#processPollingFrames(List)}. + * @param pollingLoopFilter this polling loop filter to add. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void removePollingLoopFilter(@NonNull String pollingLoopFilter) { + mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT)); + } + + /** * Sets the off host Secure Element. * @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE. * Ref: GSMA TS.26 - NFC Handset Requirements diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 81eab71fe080..0943392a68ad 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -57,6 +57,8 @@ import java.util.regex.Pattern; */ public final class CardEmulation { private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); + private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}"); + static final String TAG = "CardEmulation"; /** @@ -353,6 +355,34 @@ public final class CardEmulation { } /** + * Register a polling loop filter for a HostApduService. + * @param service The HostApduService to register the filter for. + * @param pollingLoopFilter The filter to register. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public boolean registerPollingLoopFilterForService(@NonNull ComponentName service, + @NonNull String pollingLoopFilter) { + try { + return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(), + service, pollingLoopFilter); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.registerPollingLoopFilterForService( + mContext.getUser().getIdentifier(), service, pollingLoopFilter); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** * Registers a list of AIDs for a specific category for the * specified service. * @@ -933,6 +963,24 @@ public final class CardEmulation { } /** + * Tests the validity of the polling loop filter. + * @param pollingLoopFilter The polling loop filter to test. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) { + // Verify hex characters + if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) { + Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter + + " is not a valid Polling Loop Filter."); + return false; + } + + return true; + } + + /** * A valid AID according to ISO/IEC 7816-4: * <ul> * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars) diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 2da8c8c69ff8..221ca4fd1c66 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -32,6 +32,7 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Flags; import android.os.Process; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; @@ -131,7 +132,7 @@ public class UninstallAlertDialogFragment extends DialogFragment implements final boolean isUpdate = ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); final boolean isArchive = - android.content.pm.Flags.archiving() && ( + isArchivingEnabled() && ( (dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0); final UserHandle myUserHandle = Process.myUserHandle(); UserManager userManager = getContext().getSystemService(UserManager.class); @@ -242,6 +243,11 @@ public class UninstallAlertDialogFragment extends DialogFragment implements return dialogBuilder.create(); } + private static boolean isArchivingEnabled() { + return android.content.pm.Flags.archiving() + || SystemProperties.getBoolean("pm.archiving.enabled", false); + } + private boolean isCloneProfile(UserHandle userHandle) { UserManager customUserManager = getContext() .createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0) diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp index 6f9445874fce..77cbb00427dc 100644 --- a/packages/SettingsLib/ActionBarShadow/Android.bp +++ b/packages/SettingsLib/ActionBarShadow/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibActionBarShadow", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp index 122855561751..c36b82d175e2 100644 --- a/packages/SettingsLib/ActionButtonsPreference/Android.bp +++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibActionButtonsPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp index 41de29a93e51..838a9e505ece 100644 --- a/packages/SettingsLib/ActivityEmbedding/Android.bp +++ b/packages/SettingsLib/ActivityEmbedding/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibActivityEmbedding", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp index 044ba872f3e5..67b6fb5f2ed9 100644 --- a/packages/SettingsLib/AdaptiveIcon/Android.bp +++ b/packages/SettingsLib/AdaptiveIcon/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibAdaptiveIcon", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 5da4b9518a31..c2cb75709b45 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -9,6 +9,9 @@ package { android_library { name: "SettingsLib", + defaults: [ + "SettingsLintDefaults", + ], static_libs: [ "androidx.localbroadcastmanager_localbroadcastmanager", @@ -60,8 +63,15 @@ android_library { "src/**/*.java", "src/**/*.kt", ], +} + +// defaults for lint option +java_defaults { + name: "SettingsLintDefaults", lint: { - extra_check_modules: ["SettingsLibLintChecker"], + extra_check_modules: [ + "SettingsLibLintChecker", + ], }, } diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp index 69b9d44fe16f..c5b2ef686688 100644 --- a/packages/SettingsLib/AppPreference/Android.bp +++ b/packages/SettingsLib/AppPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibAppPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp index da91344242a1..07290de8661e 100644 --- a/packages/SettingsLib/BannerMessagePreference/Android.bp +++ b/packages/SettingsLib/BannerMessagePreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibBannerMessagePreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp index be1e0cf8ab4f..448ed56a7f28 100644 --- a/packages/SettingsLib/BarChartPreference/Android.bp +++ b/packages/SettingsLib/BarChartPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibBarChartPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp index 35572fad55a2..0382829b2652 100644 --- a/packages/SettingsLib/ButtonPreference/Android.bp +++ b/packages/SettingsLib/ButtonPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibButtonPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp index 70f7554d5e53..87ec0b8d46fb 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibCollapsingToolbarBaseActivity", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp index eab35a11d7d6..279bb70d81bf 100644 --- a/packages/SettingsLib/DisplayUtils/Android.bp +++ b/packages/SettingsLib/DisplayUtils/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibDisplayUtils", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp index 17b662c60227..83f81c60c856 100644 --- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp +++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp @@ -10,13 +10,16 @@ package { android_library { name: "SettingsLibEntityHeaderWidgets", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], static_libs: [ - "androidx.annotation_annotation", - "SettingsLibSettingsTheme" + "androidx.annotation_annotation", + "SettingsLibSettingsTheme", ], sdk_version: "system_current", diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp index b45cd65467d2..d1ad80d5a4d7 100644 --- a/packages/SettingsLib/FooterPreference/Android.bp +++ b/packages/SettingsLib/FooterPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibFooterPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp index 041fce254b72..284106e96fda 100644 --- a/packages/SettingsLib/HelpUtils/Android.bp +++ b/packages/SettingsLib/HelpUtils/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibHelpUtils", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index 4d4759b99f15..6407810367cf 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibIllustrationPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp index 53ded2385634..8cf636ac8de3 100644 --- a/packages/SettingsLib/LayoutPreference/Android.bp +++ b/packages/SettingsLib/LayoutPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibLayoutPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp index 010a6ce9d4d9..b984aaf050d5 100644 --- a/packages/SettingsLib/MainSwitchPreference/Android.bp +++ b/packages/SettingsLib/MainSwitchPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibMainSwitchPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp index 155ed2e091f8..6dc07b29a510 100644 --- a/packages/SettingsLib/ProfileSelector/Android.bp +++ b/packages/SettingsLib/ProfileSelector/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibProfileSelector", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp index 3b04bd99e0f4..8d722eba49bf 100644 --- a/packages/SettingsLib/RestrictedLockUtils/Android.bp +++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp @@ -16,6 +16,9 @@ filegroup { android_library { name: "SettingsLibRestrictedLockUtils", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp index 22e4e94b80b1..c0fc741e7447 100644 --- a/packages/SettingsLib/SchedulesProvider/Android.bp +++ b/packages/SettingsLib/SchedulesProvider/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibSchedulesProvider", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp index c385d385dcc9..61ed65cbe46f 100644 --- a/packages/SettingsLib/SearchProvider/Android.bp +++ b/packages/SettingsLib/SearchProvider/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibSearchProvider", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp index 702387ecadab..2fe446d24b34 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp +++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibSelectorWithWidgetPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp index 0eec50563a75..8fed61fd3f13 100644 --- a/packages/SettingsLib/SettingsSpinner/Android.bp +++ b/packages/SettingsLib/SettingsSpinner/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibSettingsSpinner", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp index 06493c056203..e04af6c1ab11 100644 --- a/packages/SettingsLib/SettingsTransition/Android.bp +++ b/packages/SettingsLib/SettingsTransition/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibSettingsTransition", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index ed9028472fd0..df5644b8aad0 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -74,8 +74,7 @@ android:exported="false"> </provider> <activity - android:name="com.android.settingslib.spa.gallery.SpaDialogActivity" - android:excludeFromRecents="true" + android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity" android:exported="true" android:theme="@style/Theme.SpaLib.Dialog"> </activity> diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt new file mode 100644 index 000000000000..e22ed355bff1 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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.settingslib.spa.gallery + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import com.android.settingslib.spa.SpaBaseDialogActivity +import com.android.settingslib.spa.widget.dialog.AlertDialogButton +import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon + +class GalleryDialogActivity : SpaBaseDialogActivity() { + @Composable + override fun Content() { + SettingsAlertDialogWithIcon( + onDismissRequest = { finish() }, + confirmButton = AlertDialogButton("confirm") { finish() }, + dismissButton = AlertDialogButton("dismiss") { finish() }, + title = "title", + text = { + Text( + "text", + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } + ) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt deleted file mode 100644 index 8b80fe241639..000000000000 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2024 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.settingslib.spa.gallery - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.WarningAmber -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import com.android.settingslib.spa.framework.common.LogCategory -import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.widget.dialog.getDialogWidth - - -class SpaDialogActivity : ComponentActivity() { - private val spaEnvironment get() = SpaEnvironmentFactory.instance - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) - setContent { - SettingsTheme { - Content() - } - } - } - - @Composable - fun Content() { - var openAlertDialog by remember { mutableStateOf(false) } - AlertDialog(openAlertDialog) - LaunchedEffect(key1 = Unit) { - openAlertDialog = true - } - } - - @Composable - fun AlertDialog(openAlertDialog: Boolean) { - when { - openAlertDialog -> { - AlertDialogExample( - onDismissRequest = { finish() }, - onConfirmation = { finish() }, - dialogTitle = intent.getStringExtra(DIALOG_TITLE) ?: DIALOG_TITLE, - dialogText = intent.getStringExtra(DIALOG_TEXT) ?: DIALOG_TEXT, - icon = Icons.Default.WarningAmber - ) - } - } - } - - @Composable - fun AlertDialogExample( - onDismissRequest: () -> Unit, - onConfirmation: () -> Unit, - dialogTitle: String, - dialogText: String, - icon: ImageVector, - ) { - AlertDialog( - modifier = Modifier.width(getDialogWidth()), - icon = { - Icon(icon, contentDescription = null) - }, - title = { - Text(text = dialogTitle) - }, - text = { - Text(text = dialogText) - }, - onDismissRequest = { - onDismissRequest() - }, - dismissButton = { - OutlinedButton( - onClick = { - onDismissRequest() - } - ) { - Text(intent.getStringExtra(DISMISS_TEXT) ?: DISMISS_TEXT) - } - }, - confirmButton = { - Button( - onClick = { - onConfirmation() - }, - ) { - Text(intent.getStringExtra(CONFIRM_TEXT) ?: CONFIRM_TEXT) - } - } - ) - } - - companion object { - private const val TAG = "SpaDialogActivity" - private const val DIALOG_TITLE = "dialogTitle" - private const val DIALOG_TEXT = "dialogText" - private const val CONFIRM_TEXT = "confirmText" - private const val DISMISS_TEXT = "dismissText" - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt new file mode 100644 index 000000000000..dfb780af214b --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 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.settingslib.spa + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.framework.common.LogCategory +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.theme.SettingsTheme + +abstract class SpaBaseDialogActivity : ComponentActivity() { + private val spaEnvironment get() = SpaEnvironmentFactory.instance + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) + setContent { + SettingsTheme { + Content() + } + } + } + + @Composable + abstract fun Content() + + companion object { + private const val TAG = "SpaBaseDialogActivity" + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index 8ffd799385ba..de080e3d8ef4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -102,8 +102,8 @@ private fun AlertDialogPresenter.SettingsAlertDialog( fun getDialogWidth(): Dp { val configuration = LocalConfiguration.current return configuration.screenWidthDp.dp * when (configuration.orientation) { - Configuration.ORIENTATION_LANDSCAPE -> 0.6f - else -> 0.8f + Configuration.ORIENTATION_LANDSCAPE -> 0.65f + else -> 0.85f } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt new file mode 100644 index 000000000000..1695e4f33915 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 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.settingslib.spa.widget.dialog + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.WarningAmber +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.window.DialogProperties + +@Composable +fun SettingsAlertDialogWithIcon( + onDismissRequest: () -> Unit, + confirmButton: AlertDialogButton?, + dismissButton: AlertDialogButton?, + title: String?, + text: @Composable (() -> Unit)?, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) }, + modifier = Modifier.width(getDialogWidth()), + confirmButton = { + confirmButton?.let { + Button( + onClick = { + it.onClick() + }, + ) { + Text(it.text) + } + } + }, + dismissButton = dismissButton?.let { + { + OutlinedButton( + onClick = { + it.onClick() + }, + ) { + Text(it.text) + } + } + }, + title = title?.let { + { + Text( + it, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center + ) + } + }, + text = text?.let { + { + Column(Modifier.verticalScroll(rememberScrollState())) { + text() + } + } + }, + properties = DialogProperties(usePlatformDefaultWidth = false), + ) +}
\ No newline at end of file diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index abeffece2cd0..0a98791e8a6a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo +import android.os.SystemProperties import com.android.internal.R import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.async @@ -110,7 +111,7 @@ class AppListRepositoryImpl( ): List<ApplicationInfo> { val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() - val archivedPackagesFlag: Long = if (featureFlags.archiving()) + val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags)) PackageManager.MATCH_ARCHIVED_PACKAGES else 0L val regularFlags = ApplicationInfoFlags.of( disabledComponentsFlag or @@ -148,6 +149,9 @@ class AppListRepositoryImpl( } } + private fun isArchivingEnabled(featureFlags: FeatureFlags) = + featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false) + override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp index 19c59dd221c2..54b97487c3cd 100644 --- a/packages/SettingsLib/Tile/Android.bp +++ b/packages/SettingsLib/Tile/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibTile", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp index 77b7ac1246bd..e70201b0feb7 100644 --- a/packages/SettingsLib/TopIntroPreference/Android.bp +++ b/packages/SettingsLib/TopIntroPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibTopIntroPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp index 5aa906ec0ab3..70d9630d1865 100644 --- a/packages/SettingsLib/TwoTargetPreference/Android.bp +++ b/packages/SettingsLib/TwoTargetPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibTwoTargetPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp index 4cc90ccbfe80..0a83aabfce7b 100644 --- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp +++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibUsageProgressBarPreference", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp index d5a56c86431f..5d2aef780385 100644 --- a/packages/SettingsLib/Utils/Android.bp +++ b/packages/SettingsLib/Utils/Android.bp @@ -10,6 +10,9 @@ package { android_library { name: "SettingsLibUtils", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp index 390c9d2e98de..1f8d1dde15df 100644 --- a/packages/SettingsLib/search/Android.bp +++ b/packages/SettingsLib/search/Android.bp @@ -17,6 +17,9 @@ java_library { android_library { name: "SettingsLib-search", use_resource_processor: true, + defaults: [ + "SettingsLintDefaults", + ], static_libs: [ "SettingsLib-search-interface", ], diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING index 890510ffebe3..0eed2b7490d4 100644 --- a/packages/SettingsProvider/TEST_MAPPING +++ b/packages/SettingsProvider/TEST_MAPPING @@ -11,5 +11,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsDeviceConfigTestCases" + } ] } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 5004f251cfd3..8ae50eb7ffad 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -241,6 +241,8 @@ public class SecureSettings { Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, + Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY, + Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 0b0e182746e5..285c8c969343 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -387,6 +387,11 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR); + VALIDATORS.put( + Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY, + new DiscreteValueValidator(new String[] {"0", "1"})); + VALIDATORS.put( + Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index ecac5ee18582..edbc0b391b27 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -14,3 +14,11 @@ flag { bug: "311155098" is_fixed_read_only: true } + +flag { + name: "configurable_font_scale_default" + namespace: "large_screen_experiences_app_compat" + description: "Whether the font_scale is read from a device dependent configuration file" + bug: "319808237" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 507d9c467d68..3dfc4540d6e7 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -559,6 +559,9 @@ <!-- Permission required for CTS test - android.server.biometrics --> <uses-permission android:name="android.permission.TEST_BIOMETRIC" /> + <!-- Permission required for CTS test - android.server.biometrics --> + <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" /> + <!-- Permissions required for CTS test - NotificationManagerTest --> <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" /> @@ -891,6 +894,9 @@ <!-- Permission required for Cts test - CtsNotificationTestCases --> <uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" /> + <!-- Permission required for BinaryTransparencyService shell API and host test --> + <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 7443e4ccf79e..168e6e003dc6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -355,6 +355,8 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> + <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" /> + <!-- Listen to (dis-)connection of external displays and enable / disable them. --> <uses-permission android:name="android.permission.MANAGE_DISPLAYS" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index b464498d10f6..323613077a70 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -15,6 +15,13 @@ flag { } flag { + name: "notification_async_group_header_inflation" + namespace: "systemui" + description: "Inflates the notification group summary header views from the background thread." + bug: "217799515" +} + +flag { name: "notification_async_hybrid_view_inflation" namespace: "systemui" description: "Inflates hybrid (single-line) notification views from the background thread." diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index c8e18d7df63a..a390305b144e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -69,7 +69,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -90,9 +89,11 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup +import androidx.core.view.setPadding import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth import com.android.systemui.communal.ui.compose.extensions.allowGestures import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming @@ -474,7 +475,7 @@ fun HighlightedItem(size: SizeF, modifier: Modifier = Modifier) { Card( modifier = modifier.size(Dp(size.width), Dp(size.height)), colors = CardDefaults.cardColors(containerColor = Color.Transparent), - border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed), + border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed), shape = RoundedCornerShape(16.dp) ) {} } @@ -488,7 +489,7 @@ private fun CtaTileInViewModeContent( ) { val colors = LocalAndroidColorScheme.current Card( - modifier = modifier.height(size.height.dp), + modifier = modifier.height(size.height.dp).padding(CardOutlineWidth), colors = CardDefaults.cardColors( containerColor = colors.primary, @@ -560,7 +561,7 @@ private fun CtaTileInEditModeContent( } val colors = LocalAndroidColorScheme.current Card( - modifier = modifier.height(size.height.dp), + modifier = modifier.height(size.height.dp).padding(CardOutlineWidth), colors = CardDefaults.cardColors(containerColor = Color.Transparent), border = BorderStroke(1.dp, colors.primary), shape = RoundedCornerShape(200.dp), @@ -599,6 +600,7 @@ private fun WidgetContent( modifier = modifier.height(size.height.dp), contentAlignment = Alignment.Center, ) { + val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() } AndroidView( modifier = modifier.allowGestures(allowed = !viewModel.isEditMode), factory = { context -> @@ -610,9 +612,13 @@ private fun WidgetContent( model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler()) val view = model.appWidgetHost - .createView(context, model.appWidgetId, model.providerInfo) + .createViewForCommunal(context, model.appWidgetId, model.providerInfo) .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) } model.appWidgetHost.setInteractionHandler(null) + // Remove the extra padding applied to AppWidgetHostView to allow widgets to + // occupy the entire box. The added padding is now adjusted to leave only sufficient + // space for displaying the outline around the box when the widget is selected. + view.setPadding(paddingInPx) view }, // For reusing composition in lazy lists. @@ -726,6 +732,7 @@ object Dimensions { val CardHeightFull = 630.dp val CardHeightHalf = 307.dp val CardHeightThird = 199.dp + val CardOutlineWidth = 3.dp val GridHeight = CardHeightFull val Spacing = 16.dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index 900616f6af89..42fcd1363f11 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -16,23 +16,55 @@ package com.android.systemui.keyguard.ui.composable.section +import android.content.Context +import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled +import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer +import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject class NotificationSection @Inject constructor( + @Application context: Context, private val viewModel: NotificationsPlaceholderViewModel, + controller: NotificationStackScrollLayoutController, + sceneContainerFlags: SceneContainerFlags, + sharedNotificationContainer: SharedNotificationContainer, + stackScrollLayout: NotificationStackScrollLayout, + notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel, + ambientState: AmbientState, ) { + init { + if (sceneContainerFlags.flexiNotifsEnabled()) { + (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout) + sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout) + + NotificationStackAppearanceViewBinder.bind( + context, + sharedNotificationContainer, + notificationStackAppearanceViewModel, + ambientState, + controller, + ) + } + } + @Composable fun SceneScope.Notifications(modifier: Modifier = Modifier) { NotificationStack( viewModel = viewModel, - isScrimVisible = false, modifier = modifier, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 0b26ae96de54..e835d3e576d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -43,8 +44,10 @@ import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.height import com.android.systemui.notifications.ui.composable.Notifications.Form import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel +import kotlin.math.roundToInt object Notifications { object Elements { @@ -77,32 +80,52 @@ fun SceneScope.HeadsUpNotificationSpace( ) } -/** Adds the space where notification stack will appear in the scene. */ +/** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.NotificationStack( viewModel: NotificationsPlaceholderViewModel, - isScrimVisible: Boolean, + modifier: Modifier = Modifier, +) { + NotificationPlaceholder( + viewModel = viewModel, + form = Form.Stack, + modifier = modifier, + ) +} + +/** + * Adds the space where notification stack should appear in the scene, with a scrim and nested + * scrolling. + */ +@Composable +fun SceneScope.NotificationScrollingStack( + viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { val cornerRadius by viewModel.cornerRadiusDp.collectAsState() - Box(modifier = modifier) { - if (isScrimVisible) { - Box( - modifier = - Modifier.element(Notifications.Elements.NotificationScrim) - .fillMaxSize() - .graphicsLayer { - shape = RoundedCornerShape(cornerRadius.dp) - clip = true - } - .background(MaterialTheme.colorScheme.surface) - ) - } + val contentHeight by viewModel.intrinsicContentHeight.collectAsState() + + val expansionFraction by viewModel.expandFraction.collectAsState(0f) + + Box( + modifier = + modifier + .verticalNestedScrollToScene() + .fillMaxWidth() + .element(Notifications.Elements.NotificationScrim) + .graphicsLayer { + shape = RoundedCornerShape(cornerRadius.dp) + clip = true + alpha = expansionFraction + } + .background(MaterialTheme.colorScheme.surface) + .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f)) + ) { NotificationPlaceholder( viewModel = viewModel, form = Form.Stack, - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() } ) } } @@ -159,6 +182,7 @@ private fun SceneScope.NotificationPlaceholder( debugLog(viewModel) { "STACK onSizeChanged: size=$size" } } .onPlaced { coordinates: LayoutCoordinates -> + viewModel.onContentTopChanged(coordinates.positionInWindow().y) debugLog(viewModel) { "STACK onPlaced:" + " size=${coordinates.size}" + diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt index bded98d52481..747faabe514b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace +import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.Edge import com.android.systemui.scene.shared.model.SceneKey @@ -66,6 +67,7 @@ constructor( modifier: Modifier, ) { Box(modifier = modifier) { + Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim)) HeadsUpNotificationSpace( viewModel = notificationsViewModel, modifier = Modifier.padding(16.dp).fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt index 6bb525aa00fb..0c2c5195becc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt @@ -3,12 +3,12 @@ package com.android.systemui.scene.ui.composable.transitions import androidx.compose.animation.core.tween import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TransitionBuilder -import com.android.systemui.notifications.ui.composable.Notifications -import com.android.systemui.scene.ui.composable.Shade +import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.shade.ui.composable.ShadeHeader fun TransitionBuilder.goneToShadeTransition() { spec = tween(durationMillis = 500) - translate(Shade.rootElementKey, Edge.Top, true) - fade(Notifications.Elements.NotificationScrim) + fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) } + translate(QuickSettings.Elements.Content, Edge.Top, true) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 9c0f1fe0ec68..1545372686c9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -22,11 +22,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable @@ -44,10 +42,12 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.media.controls.ui.MediaCarouselController +import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.media.controls.ui.MediaHost +import com.android.systemui.media.controls.ui.MediaHostState import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL -import com.android.systemui.notifications.ui.composable.NotificationStack +import com.android.systemui.notifications.ui.composable.NotificationScrollingStack import com.android.systemui.qs.ui.composable.QuickSettings import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Direction @@ -128,6 +128,12 @@ constructor( modifier = modifier, ) + init { + mediaHost.expansion = MediaHostState.EXPANDED + mediaHost.showsOnlyActiveMedia = true + mediaHost.init(MediaHierarchyManager.LOCATION_QQS) + } + private fun destinationScenes( up: SceneKey, ): Map<UserAction, SceneModel> { @@ -148,35 +154,27 @@ private fun SceneScope.ShadeScene( mediaHost: MediaHost, modifier: Modifier = Modifier, ) { + val localDensity = LocalDensity.current val layoutWidth = remember { mutableStateOf(0) } - Box(modifier.element(Shade.Elements.Scrim)) { - Spacer( - modifier = - Modifier.element(Shade.Elements.ScrimBackground) - .fillMaxSize() - .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim) - ) + Box( + modifier = + modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim), + ) + Box { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = - Modifier.fillMaxSize() - .clickable(onClick = { viewModel.onContentClicked() }) - .padding( - start = Shade.Dimensions.HorizontalPadding, - end = Shade.Dimensions.HorizontalPadding, - bottom = 48.dp - ) + modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() }) ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, + modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) ) - Spacer(modifier = Modifier.height(16.dp)) QuickSettings( - modifier = Modifier.wrapContentHeight(), + modifier = Modifier.height(130.dp), viewModel.qsSceneAdapter, ) @@ -202,16 +200,15 @@ private fun SceneScope.ShadeScene( }, mediaHost = mediaHost, layoutWidth = layoutWidth.value, - layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(), + layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(), carouselController = mediaCarouselController, ) } Spacer(modifier = Modifier.height(16.dp)) - NotificationStack( + NotificationScrollingStack( viewModel = viewModel.notifications, - isScrimVisible = true, - modifier = Modifier.weight(1f), + modifier = Modifier.fillMaxWidth().weight(1f), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index bc3ca1bd6c56..2a793ea70292 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -47,16 +47,20 @@ import com.android.systemui.classifier.FalsingA11yDelegate import com.android.systemui.classifier.FalsingCollector import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope import com.android.systemui.log.SessionTracker import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -66,6 +70,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.JavaAdapter import com.android.systemui.util.mockito.any @@ -156,7 +161,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController private lateinit var keyguardPasswordView: KeyguardPasswordView private lateinit var testableResources: TestableResources - private lateinit var sceneTestUtils: SceneTestUtils + private lateinit var kosmos: Kosmos private lateinit var sceneInteractor: SceneInteractor private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var deviceEntryInteractor: DeviceEntryInteractor @@ -222,15 +227,15 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mSelectedUserInteractor, ) - sceneTestUtils = SceneTestUtils(this) - sceneInteractor = sceneTestUtils.sceneInteractor() + kosmos = testKosmos() + sceneInteractor = kosmos.sceneInteractor keyguardTransitionInteractor = - KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope) .keyguardTransitionInteractor sceneTransitionStateFlow = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen)) sceneInteractor.setTransitionState(sceneTransitionStateFlow) - deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor() + deviceEntryInteractor = kosmos.deviceEntryInteractor mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR) underTest = @@ -249,7 +254,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { falsingManager, userSwitcherController, featureFlags, - sceneTestUtils.fakeSceneContainerFlags, + kosmos.fakeSceneContainerFlags, globalSettings, sessionTracker, Optional.of(sideFpsController), @@ -259,7 +264,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { audioManager, faceAuthInteractor, mock(), - { JavaAdapter(sceneTestUtils.testScope.backgroundScope) }, + { JavaAdapter(kosmos.testScope.backgroundScope) }, mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, @@ -786,8 +791,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Test fun dismissesKeyguard_whenSceneChangesToGone() = - sceneTestUtils.testScope.runTest { - sceneTestUtils.fakeSceneContainerFlags.enabled = true + kosmos.testScope.runTest { + kosmos.fakeSceneContainerFlags.enabled = true // Upon init, we have never dismisses the keyguard. underTest.onInit() runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt index 27d1eb741bb0..c86c7470909b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt @@ -17,14 +17,15 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle +import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -51,6 +52,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { underTest = ColorCorrectionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -58,83 +60,78 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - 1, + SETTING_NAME, + ENABLED, testUser1.identifier ) - - underTest = - ColorCorrectionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() Truth.assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser1.identifier ) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.DISABLED, + SETTING_NAME, + DISABLED, testUser2.identifier ) - runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isFalse() + Truth.assertThat(lastValueUser2).isFalse() settings.putIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, - ColorCorrectionRepositoryImpl.ENABLED, + SETTING_NAME, + ENABLED, testUser1.identifier ) runCurrent() - Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue() - Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse() + + Truth.assertThat(lastValueUser1).isTrue() + Truth.assertThat(lastValueUser2).isFalse() } @Test @@ -146,10 +143,10 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED) + Truth.assertThat(actualValue).isEqualTo(ENABLED) } @Test @@ -161,9 +158,15 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() { val actualValue = settings.getIntForUser( - ColorCorrectionRepositoryImpl.SETTING_NAME, + SETTING_NAME, testUser1.identifier ) - Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED) + Truth.assertThat(actualValue).isEqualTo(DISABLED) } + + companion object { + private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt index 423e124bbc84..4853529229fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt @@ -21,11 +21,11 @@ import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -52,6 +52,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { underTest = ColorInversionRepositoryImpl( testDispatcher, + scope.backgroundScope, settings, ) } @@ -59,55 +60,47 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { @Test fun isEnabled_initiallyGetsSettingsValue() = scope.runTest { - settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier) + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) - underTest = - ColorInversionRepositoryImpl( - testDispatcher, - settings, - ) - - underTest.isEnabled(testUser1).launchIn(backgroundScope) + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - val actualValue: Boolean = underTest.isEnabled(testUser1).first() assertThat(actualValue).isTrue() } @Test fun isEnabled_settingUpdated_valueUpdated() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val flowValues: List<Boolean> by + collectValues(underTest.isEnabled(testUser1)) settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() + + assertThat(flowValues.size).isEqualTo(3) + assertThat(flowValues).containsExactly(false, true, false).inOrder() } @Test fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = scope.runTest { - underTest.isEnabled(testUser1).launchIn(backgroundScope) + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) - underTest.isEnabled(testUser2).launchIn(backgroundScope) settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier) - runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isFalse() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isFalse() + assertThat(lastValueUser2).isFalse() settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) runCurrent() - assertThat(underTest.isEnabled(testUser1).first()).isTrue() - assertThat(underTest.isEnabled(testUser2).first()).isFalse() + assertThat(lastValueUser1).isTrue() + assertThat(lastValueUser2).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt index b4d4e1f51251..caf92199737c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt @@ -29,10 +29,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -59,8 +62,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() { @Mock private lateinit var tableLogger: TableLogBuffer @Mock private lateinit var devicePolicyManager: DevicePolicyManager - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val clock = FakeSystemClock() private val userRepository = FakeUserRepository() private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -82,8 +85,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() { underTest = AuthenticationRepositoryImpl( applicationScope = testScope.backgroundScope, - backgroundDispatcher = testUtils.testDispatcher, - flags = testUtils.fakeSceneContainerFlags, + backgroundDispatcher = kosmos.testDispatcher, + flags = kosmos.sceneContainerFlags, clock = clock, getSecurityMode = getSecurityMode, userRepository = userRepository, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 10c16bd2f3ed..cb8cebf80767 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern @@ -29,7 +30,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,9 +47,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AuthenticationInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val underTest = utils.authenticationInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.authenticationInteractor private val onAuthenticationResult by testScope.collectLastValue(underTest.onAuthenticationResult) @@ -62,7 +64,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(authMethod).isEqualTo(Pin) assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin) - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(authMethod).isEqualTo(Password) assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password) @@ -74,7 +76,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val authMethod by collectLastValue(underTest.authenticationMethod) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None) assertThat(authMethod).isEqualTo(None) assertThat(underTest.getAuthenticationMethod()).isEqualTo(None) @@ -83,7 +85,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) } @@ -91,7 +93,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPin_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))) } @@ -99,7 +101,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test(expected = IllegalArgumentException::class) fun authenticate_withEmptyPin_throwsException() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) underTest.authenticate(listOf()) } @@ -107,7 +109,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun authenticate_withCorrectMaxLengthPin_succeeds() = testScope.runTest { val correctMaxLengthPin = List(16) { 9 } - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential(correctMaxLengthPin) } @@ -124,7 +126,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { // If the policy changes, there is work to do in SysUI. assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertFailed(underTest.authenticate(List(17) { 9 })) } @@ -132,7 +134,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertSucceeded(underTest.authenticate("password".toList())) } @@ -140,7 +142,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPassword_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertFailed(underTest.authenticate("alohomora".toList())) } @@ -148,7 +150,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPattern_succeeds() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN)) } @@ -156,7 +158,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withIncorrectPattern_fails() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) val wrongPattern = listOf( AuthenticationPatternCoordinate(x = 2, y = 0), @@ -172,7 +174,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -182,14 +184,14 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true)) assertThat(underTest.lockoutEndTimestamp).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -207,7 +209,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -225,7 +227,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) } @@ -241,7 +243,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) reportLockoutStarted(42) @@ -258,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() = testScope.runTest { - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } @@ -271,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun tryAutoConfirm_withoutCorrectPassword_returnsNull() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true)) } @@ -280,7 +282,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureDisabled_returnsFalse() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(false) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false) assertThat(isAutoConfirmEnabled).isFalse() } @@ -289,7 +291,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureEnabled_returnsTrue() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(isAutoConfirmEnabled).isTrue() } @@ -298,7 +300,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) // The feature is enabled. assertThat(isAutoConfirmEnabled).isTrue() @@ -308,7 +310,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN } assertThat(underTest.lockoutEndTimestamp).isNotNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Lockout disabled auto-confirm. assertThat(isAutoConfirmEnabled).isFalse() @@ -336,7 +338,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val failedAuthenticationAttempts by collectLastValue(underTest.failedAuthenticationAttempts) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN assertSucceeded(underTest.authenticate(correctPin)) @@ -366,7 +368,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun lockoutEndTimestamp() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN underTest.authenticate(correctPin) @@ -384,7 +386,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { val expectedLockoutEndTimestamp = testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp) - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1) // Correct PIN, but locked out, so doesn't attempt it: assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false) @@ -409,7 +411,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun upcomingWipe() = testScope.runTest { val upcomingWipe by collectLastValue(underTest.upcomingWipe) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } @@ -418,7 +420,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { var expectedFailedAttempts = 0 var remainingFailedAttempts = - utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe() + kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe() assertThat(remainingFailedAttempts) .isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) @@ -458,7 +460,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withoutAutoConfirm_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(false) } @@ -470,11 +472,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinTooShort_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) { + add(it + 1) + } } ) setAutoConfirmFeatureEnabled(true) @@ -487,28 +491,31 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) setAutoConfirmFeatureEnabled(true) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) } } ) } - assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength) + assertThat(hintedPinLength) + .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength) } @Test fun hintedPinLength_withAutoConfirmPinTooLong_isNull() = testScope.runTest { val hintedPinLength by collectLastValue(underTest.hintedPinLength) - utils.authenticationRepository.apply { + kosmos.fakeAuthenticationRepository.apply { setAuthenticationMethod(Pin) overrideCredential( buildList { - repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) } + repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) { + add(it + 1) + } } ) setAutoConfirmFeatureEnabled(true) @@ -520,10 +527,10 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withTooShortPassword() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) val tooShortPassword = buildList { - repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time -> add("$time") } } @@ -534,7 +541,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED) assertThat(onAuthenticationResult).isTrue() assertThat(underTest.lockoutEndTimestamp).isNull() - assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) + assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0) assertThat(failedAuthenticationAttempts).isEqualTo(0) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 4a39799fd64f..72e884e9e5d6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics +import android.graphics.Bitmap import android.hardware.biometrics.BiometricManager.Authenticators import android.hardware.biometrics.ComponentInfoInternal import android.hardware.biometrics.PromptContentView @@ -117,6 +118,8 @@ internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes(): } internal fun promptInfo( + logoRes: Int = -1, + logoBitmap: Bitmap? = null, title: String = "title", subtitle: String = "sub", description: String = "desc", @@ -127,6 +130,8 @@ internal fun promptInfo( negativeButton: String = "neg", ): PromptInfo { val info = PromptInfo() + info.logoRes = logoRes + info.logoBitmap = logoBitmap info.title = title info.subtitle = subtitle info.description = description diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt index c2117ae5bda4..a67b0931f171 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt @@ -20,8 +20,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -36,8 +39,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class EmergencyServicesRepositoryImplTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: EmergencyServicesRepository @@ -52,7 +55,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() { EmergencyServicesRepository( resources = context.resources, applicationScope = testScope.backgroundScope, - configurationRepository = utils.configurationRepository, + configurationRepository = kosmos.configurationRepository, ) } @@ -71,7 +74,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() { private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) { overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled) - utils.configurationRepository.onConfigurationChange() + kosmos.fakeConfigurationRepository.onConfigurationChange() runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 63581b3f7070..741cde82354a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -25,11 +25,18 @@ import com.android.internal.logging.fakeMetricsLogger import com.android.internal.logging.nano.MetricsProto import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.whenever import com.android.telecom.telecomManager @@ -54,11 +61,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var telecomManager: TelecomManager - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val metricsLogger = utils.kosmos.fakeMetricsLogger - private val activityTaskManager = utils.kosmos.activityTaskManager - private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val metricsLogger = kosmos.fakeMetricsLogger + private val activityTaskManager = kosmos.activityTaskManager + private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository @@ -68,9 +75,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - utils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true - mobileConnectionsRepository = utils.mobileConnectionsRepository + mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL) overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL) @@ -83,18 +90,18 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { .thenReturn(needsEmergencyAffordance) whenever(telecomManager.isInCall).thenReturn(false) - utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true) + kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true) - utils.telephonyRepository.setHasTelephonyRadio(true) + kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) - utils.kosmos.telecomManager = telecomManager + kosmos.telecomManager = telecomManager } @Test fun noTelephonyRadio_noButton() = testScope.runTest { - utils.telephonyRepository.setHasTelephonyRadio(false) - val underTest = utils.bouncerActionButtonInteractor() + kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false) + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) assertThat(actionButton).isNull() } @@ -102,8 +109,8 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noTelecomManager_noButton() = testScope.runTest { - utils.kosmos.telecomManager = null - val underTest = utils.bouncerActionButtonInteractor() + kosmos.telecomManager = null + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) assertThat(actionButton).isNull() } @@ -111,9 +118,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun duringCall_returnToCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) - utils.telephonyRepository.setIsInCall(true) + kosmos.fakeTelephonyRepository.setIsInCall(true) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL) @@ -133,11 +140,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_secureAuthMethod_emergencyCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = false - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNotNull() assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL) @@ -163,11 +172,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = true - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) runCurrent() assertThat(actionButton).isNotNull() @@ -179,11 +190,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_insecure_noButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = false - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) assertThat(actionButton).isNull() } @@ -191,13 +204,15 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { @Test fun noCall_simSecureButEmergencyNotSupported_noButton() = testScope.runTest { - val underTest = utils.bouncerActionButtonInteractor() + val underTest = kosmos.bouncerActionButtonInteractor val actionButton by collectLastValue(underTest.actionButton) mobileConnectionsRepository.isAnySimSecure.value = true overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false) - utils.configurationRepository.onConfigurationChange() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.telephonyRepository.setIsInCall(false) + kosmos.fakeConfigurationRepository.onConfigurationChange() + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeTelephonyRepository.setIsInCall(false) runCurrent() assertThat(actionButton).isNull() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 4b6199b55b58..707777b9f728 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -20,15 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,9 +50,9 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class BouncerInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor private lateinit var underTest: BouncerInteractor @@ -62,7 +67,7 @@ class BouncerInteractorTest : SysuiTestCase() { overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD) overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN) - underTest = utils.bouncerInteractor() + underTest = kosmos.bouncerInteractor } @Test @@ -70,7 +75,9 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() underTest.clearMessage() assertThat(message).isNull() @@ -94,7 +101,9 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun pinAuthMethod_sim_skipsAuthentication() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Sim + ) runCurrent() // We rely on TelephonyManager to authenticate the sim card. @@ -109,9 +118,11 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(isAutoConfirmEnabled).isTrue() // Incomplete input. @@ -137,7 +148,9 @@ class BouncerInteractorTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) runCurrent() // Incomplete input. @@ -160,7 +173,7 @@ class BouncerInteractorTest : SysuiTestCase() { fun passwordAuthMethod() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) runCurrent() @@ -180,7 +193,8 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat( underTest.authenticate( buildList { - repeat(utils.authenticationRepository.minPasswordLength - 1) { time -> + repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time + -> add("$time") } } @@ -198,7 +212,7 @@ class BouncerInteractorTest : SysuiTestCase() { fun patternAuthMethod() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) runCurrent() @@ -214,7 +228,8 @@ class BouncerInteractorTest : SysuiTestCase() { AuthenticationPatternCoordinate(0, 1), ) assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN) - assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength) + assertThat(wrongPattern.size) + .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength) assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED) assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN) @@ -225,7 +240,7 @@ class BouncerInteractorTest : SysuiTestCase() { val tooShortPattern = FakeAuthenticationRepository.PATTERN.subList( 0, - utils.authenticationRepository.minPatternLength - 1 + kosmos.fakeAuthenticationRepository.minPatternLength - 1 ) assertThat(underTest.authenticate(tooShortPattern)) .isEqualTo(AuthenticationResult.SKIPPED) @@ -245,7 +260,9 @@ class BouncerInteractorTest : SysuiTestCase() { val lockoutStartedEvents by collectValues(underTest.onLockoutStarted) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(lockoutStartedEvents).isEmpty() // Try the wrong PIN repeatedly, until lockout is triggered: @@ -291,17 +308,17 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun intentionalUserInputEvent_registersTouchEvent() = testScope.runTest { - assertThat(utils.powerRepository.userTouchRegistered).isFalse() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse() underTest.onIntentionalUserInput() - assertThat(utils.powerRepository.userTouchRegistered).isTrue() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue() } @Test fun intentionalUserInputEvent_notifiesFaceAuthInteractor() = testScope.runTest { val isFaceAuthRunning by - collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning) - utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted() + collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning) + kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted() runCurrent() assertThat(isFaceAuthRunning).isTrue() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt index 8c53c0e3f267..09fdd11a99dd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt @@ -28,8 +28,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -54,10 +57,10 @@ class SimBouncerInteractorTest : SysuiTestCase() { @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock lateinit var euiccManager: EuiccManager - private val utils = SceneTestUtils(this) + private val kosmos = testKosmos() private val bouncerSimRepository = FakeSimBouncerRepository() private val resources: Resources = context.resources - private val testScope = utils.testScope + private val testScope = kosmos.testScope private lateinit var underTest: SimBouncerInteractor @@ -68,13 +71,13 @@ class SimBouncerInteractorTest : SysuiTestCase() { SimBouncerInteractor( context, testScope.backgroundScope, - utils.testDispatcher, + kosmos.testDispatcher, bouncerSimRepository, telephonyManager, resources, keyguardUpdateMonitor, euiccManager, - utils.mobileConnectionsRepository, + kosmos.mobileConnectionsRepository, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 3043a710276b..27b84b2ffabc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -20,9 +20,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -33,16 +37,16 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class AuthMethodBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val bouncerInteractor = utils.bouncerInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val bouncerInteractor = kosmos.bouncerInteractor private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Pin, ) @@ -50,7 +54,9 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { fun animateFailure() = testScope.runTest { val animateFailure by collectLastValue(underTest.animateFailure) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(animateFailure).isFalse() // Wrong PIN: diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 4b1f9fee48bb..cfe8c5d52c18 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -20,15 +20,21 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlin.time.Duration.Companion.seconds @@ -50,16 +56,16 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor private lateinit var underTest: BouncerViewModel @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.bouncerViewModel() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.bouncerViewModel } @Test @@ -68,7 +74,7 @@ class BouncerViewModelTest : SysuiTestCase() { var authMethodViewModel: AuthMethodBouncerViewModel? = null authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val job = underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this) runCurrent() @@ -98,13 +104,13 @@ class BouncerViewModelTest : SysuiTestCase() { // First pass, populate our "seen" map: authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) authMethodViewModel?.let { seen[authMethod] = it } } // Second pass, assert same instances are not reused: authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) authMethodViewModel?.let { assertThat(it.authenticationMethod).isEqualTo(authMethod) assertThat(it).isNotSameInstanceAs(seen[authMethod]) @@ -116,11 +122,11 @@ class BouncerViewModelTest : SysuiTestCase() { fun authMethodUnchanged_reusesInstances() = testScope.runTest { authMethodsToTest().forEach { authMethod -> - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val firstInstance: AuthMethodBouncerViewModel? = collectLastValue(underTest.authMethodViewModel).invoke() - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) val secondInstance: AuthMethodBouncerViewModel? = collectLastValue(underTest.authMethodViewModel).invoke() @@ -139,7 +145,7 @@ class BouncerViewModelTest : SysuiTestCase() { fun message() = testScope.runTest { val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(message?.isUpdateAnimated).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -157,8 +163,8 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) val message by collectLastValue(underTest.message) - utils.authenticationRepository.setAuthenticationMethod(Pin) - assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull() + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) + assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull() assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> @@ -192,7 +198,7 @@ class BouncerViewModelTest : SysuiTestCase() { authViewModel?.isInputEnabled ?: emptyFlow() } ) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -210,7 +216,7 @@ class BouncerViewModelTest : SysuiTestCase() { testScope.runTest { val authMethodViewModel by collectLastValue(underTest.authMethodViewModel) val dialogViewModel by collectLastValue(underTest.dialogViewModel) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(authMethodViewModel?.lockoutMessageId).isNotNull() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { @@ -228,17 +234,17 @@ class BouncerViewModelTest : SysuiTestCase() { fun isSideBySideSupported() = testScope.runTest { val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported) - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isSideBySideSupported).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isSideBySideSupported).isTrue() - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isSideBySideSupported).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isSideBySideSupported).isFalse() } @@ -246,12 +252,12 @@ class BouncerViewModelTest : SysuiTestCase() { fun isFoldSplitRequired() = testScope.runTest { val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired) - utils.authenticationRepository.setAuthenticationMethod(Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(isFoldSplitRequired).isTrue() - utils.authenticationRepository.setAuthenticationMethod(Password) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password) assertThat(isFoldSplitRequired).isFalse() - utils.authenticationRepository.setAuthenticationMethod(Pattern) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern) assertThat(isFoldSplitRequired).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 5c5632f6aa7b..b3b6457b46e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,13 +19,19 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -43,12 +49,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PasswordBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor = kosmos.sceneInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val isInputEnabled = MutableStateFlow(true) private val underTest = @@ -140,10 +146,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val message by collectLastValue(bouncerViewModel.message) val password by collectLastValue(underTest.password) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) // No input entered. @@ -309,8 +315,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPasswordBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Password + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } @@ -320,13 +328,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { ) { if (isLockedOut) { repeat(failedAttemptCount) { - utils.authenticationRepository.reportAuthenticationAttempt(false) + kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false) } - utils.authenticationRepository.reportLockoutStarted( + kosmos.fakeAuthenticationRepository.reportLockoutStarted( 30.seconds.inWholeMilliseconds.toInt() ) } else { - utils.authenticationRepository.reportAuthenticationAttempt(true) + kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true) } isInputEnabled.value = !isLockedOut diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 9ee344a9527a..c2680bcc82a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -20,13 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.authenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -44,12 +51,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PatternBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val authenticationInteractor = utils.authenticationInteractor() - private val sceneInteractor = utils.sceneInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor = kosmos.sceneInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val underTest = PatternBouncerViewModel( applicationContext = context, @@ -305,7 +312,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragStart() CORRECT_PATTERN.subList( 0, - utils.authenticationRepository.minPatternLength - 1, + kosmos.authenticationRepository.minPatternLength - 1, ) .forEach { coordinate -> underTest.onDrag( @@ -374,8 +381,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPatternBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pattern + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 75e372f29590..1d660d63710d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -20,12 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -43,19 +51,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PinBouncerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val bouncerViewModel = utils.bouncerViewModel() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val bouncerViewModel = kosmos.bouncerViewModel private val underTest = PinBouncerViewModel( applicationContext = context, viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Pin, ) @@ -86,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Sim, ) @@ -97,7 +105,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun onErrorDialogDismissed_clearsDialogMessage() = testScope.runTest { val dialogMessage by collectLastValue(underTest.errorDialogMessage) - utils.simBouncerRepository.setSimVerificationErrorMessage("abc") + kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc") assertThat(dialogMessage).isEqualTo("abc") underTest.onErrorDialogDismissed() @@ -114,10 +122,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { viewModelScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), - simBouncerInteractor = utils.simBouncerInteractor, + simBouncerInteractor = kosmos.simBouncerInteractor, authenticationMethod = AuthenticationMethodModel.Sim, ) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val hintedPinLength by collectLastValue(underTest.hintedPinLength) assertThat(hintedPinLength).isNull() @@ -254,7 +262,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { @Test fun onAutoConfirm_whenCorrect() = testScope.runTest { - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult) lockDeviceAndOpenPinBouncer() @@ -269,7 +277,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { val currentScene by collectLastValue(sceneInteractor.desiredScene) val message by collectLastValue(bouncerViewModel.message) val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) lockDeviceAndOpenPinBouncer() FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit -> @@ -309,7 +317,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -318,8 +328,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -328,8 +340,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() = testScope.runTest { val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) underTest.onPinButtonClicked(1) @@ -341,7 +355,9 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown) } @@ -350,8 +366,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { fun confirmButtonAppearance_withAutoConfirm_isHidden() = testScope.runTest { val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true) assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } @@ -361,10 +379,10 @@ class PinBouncerViewModelTest : SysuiTestCase() { testScope.runTest { val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) - utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true) assertThat(isAnimationEnabled).isFalse() - utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false) assertThat(isAnimationEnabled).isTrue() } @@ -382,8 +400,8 @@ class PinBouncerViewModelTest : SysuiTestCase() { } private fun TestScope.lockDeviceAndOpenPinBouncer() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(SceneKey.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt new file mode 100644 index 000000000000..820bfbfdf0a2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 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.communal.data.repository + +import android.content.SharedPreferences +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.settings.UserFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.FakeSharedPreferences +import com.google.common.truth.Truth.assertThat +import java.io.File +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalPrefsRepositoryImplTest : SysuiTestCase() { + private lateinit var underTest: CommunalPrefsRepositoryImpl + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var userRepository: FakeUserRepository + private lateinit var userFileManager: UserFileManager + + @Before + fun setUp() { + userRepository = kosmos.fakeUserRepository + userRepository.setUserInfos(USER_INFOS) + + userFileManager = + FakeUserFileManager( + mapOf( + USER_INFOS[0].id to FakeSharedPreferences(), + USER_INFOS[1].id to FakeSharedPreferences() + ) + ) + underTest = + CommunalPrefsRepositoryImpl( + testScope.backgroundScope, + kosmos.testDispatcher, + userRepository, + userFileManager, + ) + } + + @Test + fun isCtaDismissedValue_byDefault_isFalse() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + assertThat(isCtaDismissed).isFalse() + } + + @Test + fun isCtaDismissedValue_onSet_isTrue() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + + underTest.setCtaDismissedForCurrentUser() + assertThat(isCtaDismissed).isTrue() + } + + @Test + fun isCtaDismissedValue_whenSwitchUser() = + testScope.runTest { + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + underTest.setCtaDismissedForCurrentUser() + + // dismissed true for primary user + assertThat(isCtaDismissed).isTrue() + + // switch to secondary user + userRepository.setSelectedUserInfo(USER_INFOS[1]) + + // dismissed is false for secondary user + assertThat(isCtaDismissed).isFalse() + + // switch back to primary user + userRepository.setSelectedUserInfo(USER_INFOS[0]) + + // dismissed is true for primary user + assertThat(isCtaDismissed).isTrue() + } + + private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : + UserFileManager { + override fun getFile(fileName: String, userId: Int): File { + throw UnsupportedOperationException() + } + + override fun getSharedPreferences( + fileName: String, + mode: Int, + userId: Int + ): SharedPreferences { + if (fileName != FILE_NAME) { + throw IllegalArgumentException("Preference files must be $FILE_NAME") + } + return sharedPrefs.getValue(userId) + } + } + + companion object { + val USER_INFOS = + listOf( + UserInfo(/* id= */ 0, "zero", /* flags= */ 0), + UserInfo(/* id= */ 1, "secondary", /* flags= */ 0), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 65176e1c5c0d..81d5344ed264 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -24,11 +24,12 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.scene.data.repository.SceneContainerRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.StandardTestDispatcher @@ -51,8 +52,8 @@ class CommunalRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { - val sceneTestUtils = SceneTestUtils(this) - sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository() + val kosmos = testKosmos() + sceneContainerRepository = kosmos.sceneContainerRepository featureFlagsClassic = FakeFeatureFlagsClassic() featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt index 30a5497d0a14..30a5497d0a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 4079f1241f31..1c6cecd6c838 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName @@ -32,6 +31,7 @@ import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem import com.android.systemui.communal.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.FakeLogBuffer @@ -65,7 +65,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var appWidgetManager: AppWidgetManager - @Mock private lateinit var appWidgetHost: AppWidgetHost + @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost @Mock private lateinit var userManager: UserManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index cd83c07a3b38..178eb6a42e10 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -65,6 +66,7 @@ class CommunalInteractorTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter private lateinit var underTest: CommunalInteractor @@ -84,6 +86,7 @@ class CommunalInteractorTest : SysuiTestCase() { smartspaceRepository = withDeps.smartspaceRepository keyguardRepository = withDeps.keyguardRepository editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter + communalPrefsRepository = withDeps.communalPrefsRepository underTest = withDeps.communalInteractor } @@ -331,10 +334,9 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun cta_visibilityTrue_shows() = + fun ctaTile_showsByDefault() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -346,10 +348,10 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun ctaTile_visibilityFalse_doesNotShow() = + fun ctaTile_afterDismiss_doesNotShow() = testScope.runTest { tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(false) + communalPrefsRepository.setCtaDismissedForCurrentUser() val ctaTileContent by collectLastValue(underTest.ctaTileContent) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt new file mode 100644 index 000000000000..e904236f8c3e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 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.communal.ui.widgets + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.widgets.CommunalAppWidgetHost +import com.android.systemui.communal.widgets.CommunalAppWidgetHostView +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalAppWidgetHostTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: CommunalAppWidgetHost + + @Before + fun setUp() { + underTest = CommunalAppWidgetHost(context = context, hostId = 116) + } + + @Test + fun createViewForCommunal_returnCommunalAppWidgetView() = + testScope.runTest { + val appWidgetId = 789 + val view = + underTest.createViewForCommunal( + context = context, + appWidgetId = appWidgetId, + appWidget = null + ) + assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java) + assertThat(view).isNotNull() + assertThat(view.appWidgetId).isEqualTo(appWidgetId) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 54510a82201a..09243e5282da 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.communal.view.viewmodel import android.app.Activity.RESULT_CANCELED import android.app.Activity.RESULT_OK import android.app.smartspace.SmartspaceTarget -import android.appwidget.AppWidgetHost import android.content.ComponentName import android.provider.Settings import android.widget.RemoteViews @@ -36,6 +35,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope @@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class CommunalEditModeViewModelTest : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost - @Mock private lateinit var appWidgetHost: AppWidgetHost + @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost @Mock private lateinit var uiEventLogger: UiEventLogger private val kosmos = testKosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 804c0528c46c..f9cfc3732a01 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository @@ -66,6 +67,7 @@ class CommunalViewModelTest : SysuiTestCase() { private lateinit var widgetRepository: FakeCommunalWidgetRepository private lateinit var smartspaceRepository: FakeSmartspaceRepository private lateinit var mediaRepository: FakeCommunalMediaRepository + private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository private lateinit var underTest: CommunalViewModel @@ -82,6 +84,7 @@ class CommunalViewModelTest : SysuiTestCase() { widgetRepository = withDeps.widgetRepository smartspaceRepository = withDeps.smartspaceRepository mediaRepository = withDeps.mediaRepository + communalPrefsRepository = withDeps.communalPrefsRepository underTest = CommunalViewModel( @@ -149,9 +152,6 @@ class CommunalViewModelTest : SysuiTestCase() { // Media playing. mediaRepository.mediaActive() - // CTA Tile not dismissed. - communalRepository.setCtaTileInViewModeVisibility(true) - val communalContent by collectLastValue(underTest.communalContent) // Order is smart space, then UMO, widget content and cta tile. @@ -171,7 +171,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val communalContent by collectLastValue(underTest.communalContent) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) @@ -195,7 +194,6 @@ class CommunalViewModelTest : SysuiTestCase() { fun popup_onDismiss_hidesImmediately() = testScope.runTest { tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) - communalRepository.setCtaTileInViewModeVisibility(true) val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 565049baf6f4..b54c5bdae004 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -7,9 +7,11 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever @@ -36,8 +38,8 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { @Mock private lateinit var keyguardBypassController: KeyguardBypassController @Mock private lateinit var keyguardStateController: KeyguardStateController - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val userRepository = FakeUserRepository() private val keyguardRepository = FakeKeyguardRepository() @@ -52,7 +54,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { underTest = DeviceEntryRepositoryImpl( applicationScope = testScope.backgroundScope, - backgroundDispatcher = testUtils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, userRepository = userRepository, lockPatternUtils = lockPatternUtils, keyguardBypassController = keyguardBypassController, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 929e879a5406..62d23152b77a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -20,14 +20,20 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -41,18 +47,18 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DeviceEntryInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository - private val trustRepository = utils.kosmos.fakeTrustRepository - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + private val trustRepository = kosmos.fakeTrustRepository + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor private lateinit var underTest: DeviceEntryInteractor @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.deviceEntryInteractor() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.deviceEntryInteractor } @Test @@ -65,8 +71,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.apply { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeDeviceEntryRepository.apply { setLockscreenEnabled(false) // Toggle isUnlocked, twice. @@ -99,8 +107,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Sim + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isFalse() @@ -157,10 +167,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isDeviceEntered_onBouncer_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) runCurrent() switchToScene(SceneKey.Bouncer) @@ -182,8 +192,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun canSwipeToEnter_onLockscreenWithPin_isFalse() = testScope.runTest { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) switchToScene(SceneKey.Lockscreen) val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) @@ -203,15 +215,15 @@ class DeviceEntryInteractorTest : SysuiTestCase() { } private fun setupSwipeDeviceEntryMethod() { - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) } @Test fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() = testScope.runTest { val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) switchToScene(SceneKey.Lockscreen) @@ -228,7 +240,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() = testScope.runTest { val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter) - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) switchToScene(SceneKey.Lockscreen) @@ -244,9 +256,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndSecured_true() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) @@ -256,9 +268,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndNotSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -266,9 +280,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) @@ -278,9 +292,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndNotSecured_false() = testScope.runTest { - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) assertThat(underTest.isAuthenticationRequired()).isFalse() } @@ -288,7 +304,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isBypassEnabled_enabledInRepository_true() = testScope.runTest { - utils.deviceEntryRepository.setBypassEnabled(true) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(true) assertThat(underTest.isBypassEnabled.value).isTrue() } @@ -299,8 +315,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.attemptDeviceEntry() @@ -315,7 +333,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) underTest.attemptDeviceEntry() @@ -329,8 +349,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { switchToScene(SceneKey.Lockscreen) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) underTest.attemptDeviceEntry() @@ -340,7 +362,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isBypassEnabled_disabledInRepository_false() = testScope.runTest { - utils.deviceEntryRepository.setBypassEnabled(false) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(false) assertThat(underTest.isBypassEnabled.value).isFalse() } @@ -348,8 +370,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() { fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() = testScope.runTest { val isUnlocked by collectLastValue(underTest.isUnlocked) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) assertThat(isUnlocked).isFalse() authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 8933d2c93f5a..2c3afb1b40a9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -26,12 +26,16 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -48,10 +52,10 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class KeyguardInteractorTest : SysuiTestCase() { - private val testUtils = SceneTestUtils(this) - private val testScope = testUtils.testScope - private val repository = testUtils.keyguardRepository - private val sceneInteractor = testUtils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val repository = kosmos.fakeKeyguardRepository + private val sceneInteractor = kosmos.sceneInteractor private val commandQueue = FakeCommandQueue() private val bouncerRepository = FakeKeyguardBouncerRepository() private val shadeRepository = FakeShadeRepository() @@ -63,7 +67,7 @@ class KeyguardInteractorTest : SysuiTestCase() { repository = repository, commandQueue = commandQueue, powerInteractor = PowerInteractorFactory.create().powerInteractor, - sceneContainerFlags = testUtils.fakeSceneContainerFlags, + sceneContainerFlags = kosmos.fakeSceneContainerFlags, bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), shadeRepository = shadeRepository, @@ -183,7 +187,7 @@ class KeyguardInteractorTest : SysuiTestCase() { @Test fun animationDozingTransitions() = testScope.runTest { - testUtils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true val isAnimate by collectLastValue(underTest.animateDozingTransitions) underTest.setAnimateDozingTransitions(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 04e90c8deebf..aa15d0befa80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -21,11 +21,19 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,9 +45,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LockscreenSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val underTest = createLockscreenSceneViewModel() @@ -47,9 +55,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_canSwipeToUnlock_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) @@ -59,8 +69,10 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer) @@ -69,7 +81,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun leftTransitionSceneKey_communalIsEnabled_communal() = testScope.runTest { - utils.communalRepository.setIsCommunalEnabled(true) + kosmos.fakeCommunalRepository.setIsCommunalEnabled(true) val underTest = createLockscreenSceneViewModel() assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal) @@ -78,7 +90,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { @Test fun leftTransitionSceneKey_communalIsDisabled_null() = testScope.runTest { - utils.communalRepository.setIsCommunalEnabled(false) + kosmos.fakeCommunalRepository.setIsCommunalEnabled(false) val underTest = createLockscreenSceneViewModel() assertThat(underTest.leftDestinationSceneKey).isNull() @@ -87,13 +99,13 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel { return LockscreenSceneViewModel( applicationScope = testScope.backgroundScope, - deviceEntryInteractor = utils.deviceEntryInteractor(), - communalInteractor = utils.communalInteractor(), + deviceEntryInteractor = kosmos.deviceEntryInteractor, + communalInteractor = kosmos.communalInteractor, longPress = KeyguardLongPressViewModel( interactor = mock(), ), - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 6403ed19a9b2..be523b813494 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -22,13 +22,15 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Direction import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.scene.shared.model.UserAction import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -36,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest @@ -47,9 +50,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QuickSettingsSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() } @@ -90,7 +93,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { QuickSettingsSceneViewModel( shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index ecc2ef15dec8..1cd764e01bad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -27,22 +27,38 @@ import com.android.internal.util.EmergencyAffordanceManager import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor +import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel +import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel +import com.android.systemui.classifier.domain.interactor.falsingInteractor +import com.android.systemui.classifier.falsingCollector +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.pipeline.MediaDataManager -import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel @@ -50,13 +66,17 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -101,13 +121,13 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class SceneFrameworkIntegrationTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope - private val sceneContainerConfig = utils.fakeSceneContainerConfig() - private val sceneInteractor = utils.sceneInteractor() - private val authenticationInteractor = utils.authenticationInteractor() - private val deviceEntryInteractor = utils.deviceEntryInteractor() - private val communalInteractor = utils.communalInteractor() + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope + private val sceneContainerConfig = kosmos.sceneContainerConfig + private val sceneInteractor = kosmos.sceneInteractor + private val authenticationInteractor = kosmos.authenticationInteractor + private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val communalInteractor = kosmos.communalInteractor private val transitionState = MutableStateFlow<ObservableTransitionState>( @@ -116,11 +136,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private val sceneContainerViewModel = SceneContainerViewModel( sceneInteractor = sceneInteractor, - falsingInteractor = utils.falsingInteractor(), + falsingInteractor = kosmos.falsingInteractor, ) .apply { setTransitionState(transitionState) } - private val bouncerInteractor = utils.bouncerInteractor() + private val bouncerInteractor = kosmos.bouncerInteractor private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor @@ -135,7 +155,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { KeyguardLongPressViewModel( interactor = mock(), ), - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, ) private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) @@ -152,22 +172,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { FakeMobileConnectionsRepository(), ), constants = mock(), - utils.fakeFeatureFlags, + flags = kosmos.fakeFeatureFlagsClassic, scope = testScope.backgroundScope, ) private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel private lateinit var shadeSceneViewModel: ShadeSceneViewModel - private val keyguardInteractor = utils.keyguardInteractor() - private val powerInteractor = utils.powerInteractor() + private val keyguardInteractor = kosmos.keyguardInteractor + private val powerInteractor = kosmos.powerInteractor private var bouncerSceneJob: Job? = null private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() }) @Mock private lateinit var mediaDataManager: MediaDataManager - @Mock private lateinit var mediaHost: MediaHost private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager private lateinit var telecomManager: TelecomManager @@ -177,27 +196,27 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true) - telecomManager = checkNotNull(utils.kosmos.telecomManager) + telecomManager = checkNotNull(kosmos.telecomManager) whenever(telecomManager.isInCall).thenReturn(false) - emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager + emergencyAffordanceManager = kosmos.emergencyAffordanceManager whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true) - utils.fakeFeatureFlags.apply { + kosmos.fakeFeatureFlagsClassic.apply { set(Flags.NEW_NETWORK_SLICE_UI, false) set(Flags.REFACTOR_GETCURRENTUSER, true) } - mobileConnectionsRepository = utils.mobileConnectionsRepository + mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository mobileConnectionsRepository.isAnySimSecure.value = false - utils.telephonyRepository.apply { + kosmos.fakeTelephonyRepository.apply { setHasTelephonyRadio(true) setCallState(TelephonyManager.CALL_STATE_IDLE) setIsInCall(false) } - bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor() - bouncerViewModel = utils.bouncerViewModel() + bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor + bouncerViewModel = kosmos.bouncerViewModel shadeHeaderViewModel = ShadeHeaderViewModel( @@ -215,12 +234,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, mediaDataManager = mediaDataManager, - mediaHost = mediaHost, ) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) val displayTracker = FakeDisplayTracker(context) val sysUiState = SysUiState(displayTracker) @@ -230,15 +248,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, deviceEntryInteractor = deviceEntryInteractor, keyguardInteractor = keyguardInteractor, - flags = utils.fakeSceneContainerFlags, + flags = kosmos.fakeSceneContainerFlags, sysUiState = sysUiState, displayId = displayTracker.defaultDisplayId, sceneLogger = mock(), - falsingCollector = utils.falsingCollector(), + falsingCollector = kosmos.falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, - authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }, + simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor }, + authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor }, windowController = mock(), ) startable.start() @@ -534,15 +552,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit // is not an observable that can trigger a new evaluation. - utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen) - utils.authenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) runCurrent() } /** Emulates a phone call in progress. */ private fun TestScope.startPhoneCall() { whenever(telecomManager.isInCall).thenReturn(true) - utils.telephonyRepository.apply { + kosmos.fakeTelephonyRepository.apply { setHasTelephonyRadio(true) setIsInCall(true) setCallState(TelephonyManager.CALL_STATE_OFFHOOK) @@ -651,7 +669,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(authMethod.isSecure) .isTrue() - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() } @@ -665,7 +683,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { enterPin() // This repository state is not changed by the AuthInteractor, it relies on // KeyguardStateController. - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) emulateUiSceneTransition( expectedVisible = false, ) @@ -721,7 +739,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } pinBouncerViewModel.onAuthenticateButtonClicked() setAuthMethod(authMethodAfterSimUnlock) - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() } @@ -768,7 +786,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private fun TestScope.introduceLockedSim() { setAuthMethod(AuthenticationMethodModel.Sim) - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true runCurrent() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 339d0263a089..b267720971cd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -22,11 +22,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -39,12 +42,12 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneContainerRepositoryTest : SysuiTestCase() { - private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true } - private val testScope = utils.testScope + private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } + private val testScope = kosmos.testScope @Test fun allSceneKeys() { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository assertThat(underTest.allSceneKeys()) .isEqualTo( listOf( @@ -61,7 +64,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun desiredScene() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val currentScene by collectLastValue(underTest.desiredScene) assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen)) @@ -71,15 +74,15 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test(expected = IllegalStateException::class) fun setDesiredScene_noSuchSceneInContainer_throws() { - utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) - val underTest = utils.fakeSceneContainerRepository() + kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen) + val underTest = kosmos.sceneContainerRepository underTest.setDesiredScene(SceneModel(SceneKey.Shade)) } @Test fun isVisible() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val isVisible by collectLastValue(underTest.isVisible) assertThat(isVisible).isTrue() @@ -93,19 +96,19 @@ class SceneContainerRepositoryTest : SysuiTestCase() { @Test fun transitionState_defaultsToIdle() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState by collectLastValue(underTest.transitionState) assertThat(transitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } @Test fun transitionState_reflectsUpdates() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -134,7 +137,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.setTransitionState(null) assertThat(reflectedTransitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 486f7ab6501d..d159986015a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -20,10 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.sceneContainerConfig +import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -36,20 +42,20 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneInteractorTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: SceneInteractor @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true - underTest = utils.sceneInteractor() + kosmos.fakeSceneContainerFlags.enabled = true + underTest = kosmos.sceneInteractor } @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys) } @Test @@ -75,7 +81,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun transitionState() = testScope.runTest { - val underTest = utils.fakeSceneContainerRepository() + val underTest = kosmos.sceneContainerRepository val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -104,7 +110,7 @@ class SceneInteractorTest : SysuiTestCase() { underTest.setTransitionState(null) assertThat(reflectedTransitionState) .isEqualTo( - ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey) + ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } @@ -348,8 +354,8 @@ class SceneInteractorTest : SysuiTestCase() { @Test fun userInput() = testScope.runTest { - assertThat(utils.powerRepository.userTouchRegistered).isFalse() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse() underTest.onUserInput() - assertThat(utils.powerRepository.userTouchRegistered).isTrue() + assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt index 8be4eeb7be7a..f23716ccca54 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.scene.domain.interactor +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.IStatusBarService @@ -28,7 +30,11 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -37,6 +43,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -50,6 +57,7 @@ import org.mockito.Mockito.verify class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val testScope = TestScope() + private val testDispatcher = StandardTestDispatcher() private val iStatusBarService = mock<IStatusBarService>() private val executor = FakeExecutor(FakeSystemClock()) private val windowRootViewVisibilityRepository = @@ -59,6 +67,9 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { private val notificationPresenter = mock<NotificationPresenter>() private val notificationsController = mock<NotificationsController>() private val powerInteractor = PowerInteractorFactory.create().powerInteractor + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) private val underTest = WindowRootViewVisibilityInteractor( @@ -67,6 +78,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) .apply { setUp(notificationPresenter, notificationsController) } @@ -257,7 +269,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() = testScope.runTest { underTest.start() @@ -273,6 +286,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() = + testScope.runTest { + underTest.start() + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + activeNotificationsRepository.setActiveNotifs(4) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(1) + } + + @Test fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() = testScope.runTest { underTest.start() @@ -288,7 +318,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() = + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) @@ -304,7 +335,25 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test - fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() = + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true) + + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(9) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(9) + } + + @Test + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() = testScope.runTest { underTest.start() whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) @@ -320,6 +369,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() = + testScope.runTest { + underTest.start() + whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true) + + whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false) + activeNotificationsRepository.setActiveNotifs(8) + + makeLockscreenShadeVisible() + + val notifCount = argumentCaptor<Int>() + verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture()) + assertThat(notifCount.value).isEqualTo(8) + } + + @Test fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 5fe4ca1fbfe3..4afa5f2a44b9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -25,19 +25,31 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.testScope import com.android.systemui.model.SysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -65,15 +77,15 @@ class SceneContainerStartableTest : SysuiTestCase() { @Mock private lateinit var windowController: NotificationShadeWindowController - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val sceneContainerFlags = utils.fakeSceneContainerFlags - private val authenticationInteractor = utils.authenticationInteractor() - private val bouncerInteractor = utils.bouncerInteractor() - private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository - private val deviceEntryInteractor = utils.deviceEntryInteractor() - private val keyguardInteractor = utils.keyguardInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val sceneContainerFlags = kosmos.fakeSceneContainerFlags + private val authenticationInteractor = kosmos.authenticationInteractor + private val bouncerInteractor = kosmos.bouncerInteractor + private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + private val deviceEntryInteractor = kosmos.deviceEntryInteractor + private val keyguardInteractor = kosmos.keyguardInteractor private val sysUiState: SysUiState = mock() private val falsingCollector: FalsingCollector = mock() private val powerInteractor = PowerInteractorFactory.create().powerInteractor @@ -97,7 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() { falsingCollector = falsingCollector, powerInteractor = powerInteractor, bouncerInteractor = bouncerInteractor, - simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor }, + simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor }, authenticationInteractor = dagger.Lazy { authenticationInteractor }, windowController = windowController, ) @@ -172,7 +184,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) underTest.start() - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -188,7 +200,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -204,7 +216,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -222,7 +234,7 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -244,7 +256,7 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade) assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Shade) @@ -263,7 +275,7 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) } @@ -373,7 +385,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) underTest.start() - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() powerInteractor.setAwakeForTest() runCurrent() @@ -463,11 +475,11 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).setShowingAod(false) - utils.keyguardRepository.setIsDozing(true) + kosmos.fakeKeyguardRepository.setIsDozing(true) runCurrent() verify(falsingCollector).setShowingAod(true) - utils.keyguardRepository.setIsDozing(false) + kosmos.fakeKeyguardRepository.setIsDozing(false) runCurrent() verify(falsingCollector, times(2)).setShowingAod(false) } @@ -493,7 +505,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodUnavailable() = testScope.runTest { - utils.keyguardRepository.setAodAvailable(false) + kosmos.fakeKeyguardRepository.setAodAvailable(false) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -541,7 +553,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun collectFalsingSignals_screenOnAndOff_aodAvailable() = testScope.runTest { - utils.keyguardRepository.setAodAvailable(true) + kosmos.fakeKeyguardRepository.setAodAvailable(true) runCurrent() prepareState( initialSceneKey = SceneKey.Lockscreen, @@ -619,7 +631,7 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer) @@ -628,7 +640,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchesToLockscreen_whenSimBecomesUnlocked() = testScope.runTest { - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( @@ -638,7 +650,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ) underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen) @@ -647,7 +659,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() = testScope.runTest { - utils.mobileConnectionsRepository.isAnySimSecure.value = true + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key }) prepareState( @@ -658,7 +670,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ) underTest.start() runCurrent() - utils.mobileConnectionsRepository.isAnySimSecure.value = false + kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() assertThat(currentSceneKey).isEqualTo(SceneKey.Gone) @@ -730,8 +742,8 @@ class SceneContainerStartableTest : SysuiTestCase() { } } sceneContainerFlags.enabled = true - utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked) - utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled) + kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked) + kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled) val transitionStateFlow = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(SceneKey.Lockscreen) @@ -743,8 +755,8 @@ class SceneContainerStartableTest : SysuiTestCase() { sceneInteractor.onSceneChanged(SceneModel(it), "reason") } authenticationMethod?.let { - utils.authenticationRepository.setAuthenticationMethod(authenticationMethod) - utils.deviceEntryRepository.setLockscreenEnabled( + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled( isLockscreenEnabled = isLockscreenEnabled ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 3a4ee642685f..ede453d85ee1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -21,10 +21,14 @@ package com.android.systemui.scene.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.sceneKeys +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -36,17 +40,17 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SceneContainerViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val interactor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val interactor = kosmos.sceneInteractor private lateinit var underTest: SceneContainerViewModel @Before fun setUp() { - utils.fakeSceneContainerFlags.enabled = true + kosmos.fakeSceneContainerFlags.enabled = true underTest = SceneContainerViewModel( sceneInteractor = interactor, - falsingInteractor = utils.falsingInteractor(), + falsingInteractor = kosmos.falsingInteractor, ) } @@ -64,7 +68,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { @Test fun allSceneKeys() { - assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys()) + assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 77ddf15c41ad..51745023c5be 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -7,7 +7,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.ObservableTransitionState import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -18,6 +19,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow @@ -31,9 +33,9 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class ShadeHeaderViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index a8133a3a4de3..e9801652f060 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -19,16 +19,20 @@ package com.android.systemui.shade.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.pipeline.MediaDataManager -import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -36,6 +40,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -53,10 +58,10 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class ShadeSceneViewModelTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() - private val deviceEntryInteractor = utils.deviceEntryInteractor() + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val deviceEntryInteractor = kosmos.deviceEntryInteractor private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } @@ -84,7 +89,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private lateinit var underTest: ShadeSceneViewModel @Mock private lateinit var mediaDataManager: MediaDataManager - @Mock private lateinit var mediaHost: MediaHost @Before fun setUp() { @@ -105,9 +109,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() { deviceEntryInteractor = deviceEntryInteractor, shadeHeaderViewModel = shadeHeaderViewModel, qsSceneAdapter = qsFlexiglassAdapter, - notifications = utils.notificationsPlaceholderViewModel(), + notifications = kosmos.notificationsPlaceholderViewModel, mediaDataManager = mediaDataManager, - mediaHost = mediaHost, ) } @@ -115,8 +118,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceLocked_lockScreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen) } @@ -125,8 +130,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_deviceUnlocked_gone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone) } @@ -135,8 +142,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason") @@ -147,8 +156,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey) - utils.deviceEntryRepository.setLockscreenEnabled(true) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason") sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") @@ -159,8 +170,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceUnlocked_switchesToGone() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(true) runCurrent() underTest.onContentClicked() @@ -172,8 +185,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() { fun onContentClicked_deviceLockedSecurely_switchesToBouncer() = testScope.runTest { val currentScene by collectLastValue(sceneInteractor.desiredScene) - utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - utils.deviceEntryRepository.setUnlocked(false) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() underTest.onContentClicked() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt index 262795fe0eb6..8e8e5106e93c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt @@ -24,12 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.telephony.TelephonyListenerManager +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -39,7 +40,6 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class TelephonyRepositoryImplTest : SysuiTestCase() { @@ -47,8 +47,8 @@ class TelephonyRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var manager: TelephonyListenerManager @Mock private lateinit var telecomManager: TelecomManager - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var underTest: TelephonyRepositoryImpl @@ -61,7 +61,7 @@ class TelephonyRepositoryImplTest : SysuiTestCase() { TelephonyRepositoryImpl( applicationScope = testScope.backgroundScope, applicationContext = context, - backgroundDispatcher = utils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, manager = manager, telecomManager = telecomManager, ) diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml index 23fbb12f3036..10f71134c4cc 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml @@ -20,6 +20,13 @@ android:layout_height="wrap_content" android:orientation="vertical"> + <ImageView + android:id="@+id/logo" + android:layout_width="@dimen/biometric_auth_icon_size" + android:layout_height="@dimen/biometric_auth_icon_size" + android:layout_gravity="center" + android:scaleType="fitXY"/> + <TextView android:id="@+id/title" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index 85b6e8dc12b3..5db9eee6a908 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -50,7 +50,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView android:id="@+id/turbulence_noise_view" @@ -59,7 +62,10 @@ app:layout_constraintStart_toStartOf="@id/album_art" app:layout_constraintEnd_toEndOf="@id/album_art" app:layout_constraintTop_toTopOf="@id/album_art" - app:layout_constraintBottom_toBottomOf="@id/album_art" /> + app:layout_constraintBottom_toBottomOf="@id/album_art" + android:clipToOutline="true" + android:background="@drawable/qs_media_outline_layout_bg" + /> <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml index be34a48d1cd6..0955f678dfd4 100644 --- a/packages/SystemUI/res/values-sw720dp-land/config.xml +++ b/packages/SystemUI/res/values-sw720dp-land/config.xml @@ -24,9 +24,6 @@ <!-- The maximum number of tiles in the QuickQSPanel --> <integer name="quick_qs_panel_max_tiles">6</integer> - <!-- Whether to use the split 2-column notification shade --> - <bool name="config_use_split_notification_shade">true</bool> - <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 798fc06b44f7..ee2a1ceab2b7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -897,6 +897,10 @@ <dimen name="communal_tutorial_indicator_padding">24dp</dimen> <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen> + <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub. + Keep it the same as in Launcher--> + <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen> + <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> <dimen name="keyguard_lock_padding">20dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6e035e8c8c36..ec4c7d5bf67e 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -249,6 +249,9 @@ --> <item type="id" name="tag_smartspace_view" /> + <!-- ID of the Scene Container root Composable view --> + <item type='id' name="scene_container_root_composable" /> + <!-- Tag set on the Compose implementation of the QS footer actions. --> <item type="id" name="tag_compose_qs_footer_actions" /> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt index 6483ae44d5ec..c7e5b645db5f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color correction. */ @@ -45,22 +49,24 @@ class ColorCorrectionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorCorrectionRepository { - companion object { - const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED - const val DISABLED = 0 - const val ENABLED = 1 - } + private val userMap = mutableMapOf<Int, Flow<Boolean>>() override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { @@ -70,4 +76,10 @@ constructor( userHandle.identifier ) } + + companion object { + private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt index bbf10c509e50..419eada91f87 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt @@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository import android.os.UserHandle import android.provider.Settings.Secure import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext /** Provides data related to color inversion. */ @@ -45,16 +49,24 @@ class ColorInversionRepositoryImpl @Inject constructor( @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, private val secureSettings: SecureSettings, ) : ColorInversionRepository { + private val userMap = mutableMapOf<Int, Flow<Boolean>>() + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = - secureSettings - .observerFlow(userHandle.identifier, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED } - .distinctUntilChanged() - .flowOn(bgCoroutineContext) + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1) + } override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = withContext(bgCoroutineContext) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index ab23564a1df4..57e308ff16e8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -399,7 +399,8 @@ public class AuthContainerView extends LinearLayout config.mPromptInfo, config.mUserId, config.mOperationId, - new BiometricModalities(fpProps, faceProps)); + new BiometricModalities(fpProps, faceProps), + config.mOpPackageName); final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( R.layout.biometric_prompt_layout, null, false); @@ -470,7 +471,8 @@ public class AuthContainerView extends LinearLayout mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication( - mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId); + mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId, + mConfig.mOpPackageName); final CredentialViewModel vm = mCredentialViewModelProvider.get(); vm.setAnimateContents(animateContents); ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 86802a5b58b0..02eae9cedf74 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -148,6 +148,12 @@ public class UdfpsDialogMeasureAdapter { || child.getId() == R.id.customized_view_container) { //skip description view and compute later continue; + } else if (child.getId() == R.id.logo) { + child.measure( + MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, + MeasureSpec.EXACTLY)); } else { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index b35fbbc7bb32..ad7bb0e61178 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -55,6 +55,9 @@ interface PromptRepository { /** The kind of credential to use (biometric, pin, pattern, etc.). */ val kind: StateFlow<PromptKind> + /** The package name that the prompt is called from. */ + val opPackageName: StateFlow<String?> + /** * If explicit confirmation is required. * @@ -68,6 +71,7 @@ interface PromptRepository { userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, + opPackageName: String, ) /** Unset the prompt info. */ @@ -108,6 +112,9 @@ constructor( private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) override val kind = _kind.asStateFlow() + private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) + override val opPackageName = _opPackageName.asStateFlow() + private val _faceSettings = _userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged() private val _faceSettingAlwaysRequireConfirmation = @@ -127,11 +134,13 @@ constructor( userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, + opPackageName: String, ) { _kind.value = kind _userId.value = userId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo + _opPackageName.value = opPackageName } override fun unsetPrompt() { @@ -139,6 +148,7 @@ constructor( _userId.value = null _challenge.value = null _kind.value = PromptKind.Biometric() + _opPackageName.value = null } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index ac4b717a23ec..359e2e703ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -115,12 +115,14 @@ constructor( @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) { biometricPromptRepository.setPrompt( promptInfo, userId, challenge, - kind.asBiometricPromptCredential() + kind.asBiometricPromptCredential(), + opPackageName, ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 65a2c0a2490f..b3f95748ccdc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -76,6 +76,7 @@ interface PromptSelectorInteractor { userId: Int, challenge: Long, modalities: BiometricModalities, + opPackageName: String, ) /** Use credential-based authentication instead of biometrics. */ @@ -84,6 +85,7 @@ interface PromptSelectorInteractor { @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) /** Unset the current authentication request. */ @@ -104,9 +106,12 @@ constructor( promptRepository.promptInfo, promptRepository.challenge, promptRepository.userId, - promptRepository.kind - ) { promptInfo, challenge, userId, kind -> - if (promptInfo == null || userId == null || challenge == null) { + promptRepository.kind, + promptRepository.opPackageName, + ) { promptInfo, challenge, userId, kind, opPackageName -> + if ( + promptInfo == null || userId == null || challenge == null || opPackageName == null + ) { return@combine null } @@ -117,6 +122,7 @@ constructor( userInfo = BiometricUserInfo(userId = userId), operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge), modalities = kind.activeModalities, + opPackageName = opPackageName, ) else -> null } @@ -152,13 +158,15 @@ constructor( promptInfo: PromptInfo, userId: Int, challenge: Long, - modalities: BiometricModalities + modalities: BiometricModalities, + opPackageName: String, ) { promptRepository.setPrompt( promptInfo = promptInfo, userId = userId, gatekeeperChallenge = challenge, kind = PromptKind.Biometric(modalities), + opPackageName = opPackageName, ) } @@ -167,12 +175,14 @@ constructor( @Utils.CredentialType kind: Int, userId: Int, challenge: Long, + opPackageName: String, ) { promptRepository.setPrompt( promptInfo = promptInfo, userId = userId, gatekeeperChallenge = challenge, kind = kind.asBiometricPromptCredential(), + opPackageName = opPackageName, ) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 437793798567..c17c8dced668 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -1,5 +1,6 @@ package com.android.systemui.biometrics.domain.model +import android.graphics.Bitmap import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo import com.android.systemui.biometrics.shared.model.BiometricModalities @@ -26,6 +27,7 @@ sealed class BiometricPromptRequest( userInfo: BiometricUserInfo, operationInfo: BiometricOperationInfo, val modalities: BiometricModalities, + val opPackageName: String, ) : BiometricPromptRequest( title = info.title?.toString() ?: "", @@ -36,6 +38,8 @@ sealed class BiometricPromptRequest( showEmergencyCallButton = info.isShowEmergencyCallButton ) { val contentView: PromptContentView? = info.contentView + val logoRes: Int = info.logoRes + val logoBitmap: Bitmap? = info.logoBitmap val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java index 60b454e9670e..b450896729b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java @@ -115,6 +115,12 @@ public class BiometricPromptLayout extends LinearLayout { MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height, MeasureSpec.EXACTLY)); + } else if (child.getId() == R.id.logo) { + child.measure( + MeasureSpec.makeMeasureSpec(child.getLayoutParams().width, + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(child.getLayoutParams().height, + MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.biometric_icon) { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 04dc7a8da9f3..285ab4a800b6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -31,6 +31,7 @@ import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO import android.view.accessibility.AccessibilityManager import android.widget.Button +import android.widget.ImageView import android.widget.ScrollView import android.widget.TextView import androidx.lifecycle.DefaultLifecycleObserver @@ -92,6 +93,7 @@ object BiometricViewBinder { val textColorHint = view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme) + val logoView = view.requireViewById<ImageView>(R.id.logo) val titleView = view.requireViewById<TextView>(R.id.title) val subtitleView = view.requireViewById<TextView>(R.id.subtitle) val descriptionView = view.requireViewById<TextView>(R.id.description) @@ -99,6 +101,8 @@ object BiometricViewBinder { view.requireViewById<ScrollView>(R.id.customized_view_container) // set selected to enable marquee unless a screen reader is enabled + logoView.isSelected = + !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled titleView.isSelected = !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled subtitleView.isSelected = @@ -152,6 +156,7 @@ object BiometricViewBinder { } } + logoView.setImageDrawable(viewModel.logo.first()) titleView.text = viewModel.title.first() subtitleView.text = viewModel.subtitle.first() descriptionView.text = viewModel.description.first() @@ -183,6 +188,7 @@ object BiometricViewBinder { viewModel = viewModel, viewsToHideWhenSmall = listOf( + logoView, titleView, subtitleView, descriptionView, @@ -190,6 +196,7 @@ object BiometricViewBinder { ), viewsToFadeInOnSizeChange = listOf( + logoView, titleView, subtitleView, descriptionView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index c3bbaedb2670..d5695f31f121 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -25,6 +25,7 @@ import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowManager import android.view.accessibility.AccessibilityManager +import android.widget.ImageView import android.widget.TextView import androidx.core.animation.addListener import androidx.core.view.doOnLayout @@ -234,7 +235,13 @@ private fun View.isLandscape(): Boolean { private fun View.showContentOrHide(forceHide: Boolean = false) { val isTextViewWithBlankText = this is TextView && this.text.isBlank() - visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE + val isImageViewWithoutImage = this is ImageView && this.drawable == null + visibility = + if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) { + View.GONE + } else { + View.VISIBLE + } } private fun View.asVerticalAnimator( diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 1c789283ec70..dca0338dc8e7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -18,6 +18,8 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.Context import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.PromptContentView import android.util.Log @@ -233,6 +235,19 @@ constructor( } } + /** Logo for the prompt. */ + val logo: Flow<Drawable?> = + promptSelectorInteractor.prompt + .map { + when { + it == null -> null + it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme) + it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) + else -> context.packageManager.getApplicationIcon(it.opPackageName) + } + } + .distinctUntilChanged() + /** Title for the prompt. */ val title: Flow<String> = promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 10768ea6122a..dc07c1b25678 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.dagger import com.android.systemui.communal.data.db.CommunalDatabaseModule import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule import com.android.systemui.communal.data.repository.CommunalRepositoryModule import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule @@ -34,6 +35,7 @@ import dagger.Module CommunalTutorialRepositoryModule::class, CommunalWidgetRepositoryModule::class, CommunalDatabaseModule::class, + CommunalPrefsRepositoryModule::class, ] ) interface CommunalModule { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt new file mode 100644 index 000000000000..c2ea2e93ce58 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 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.communal.data.repository + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences +import com.android.systemui.user.data.repository.UserRepository +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** + * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA + * tile been dismissed?" + */ +interface CommunalPrefsRepository { + + /** Whether the CTA tile has been dismissed. */ + val isCtaDismissed: Flow<Boolean> + + /** Save the CTA tile dismissed state for the current user. */ + suspend fun setCtaDismissedForCurrentUser() +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalPrefsRepositoryImpl +@Inject +constructor( + @Background private val backgroundScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, + private val userRepository: UserRepository, + private val userFileManager: UserFileManager, +) : CommunalPrefsRepository { + + override val isCtaDismissed: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest(::observeCtaDismissState) + .stateIn( + scope = backgroundScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + override suspend fun setCtaDismissedForCurrentUser() = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .edit() + .putBoolean(CTA_DISMISSED_STATE, true) + .apply() + } + + private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> = + userFileManager + .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id) + // Emit at the start of collection to ensure we get an initial value + .onStart { emit(Unit) } + .map { getCtaDismissedState() } + .flowOn(bgDispatcher) + + private suspend fun getCtaDismissedState(): Boolean = + withContext(bgDispatcher) { + getSharedPrefsForUser(userRepository.getSelectedUserInfo()) + .getBoolean(CTA_DISMISSED_STATE, false) + } + + private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + user.id, + ) + } + + companion object { + const val TAG = "CommunalRepository" + const val FILE_NAME = "communal_hub_prefs" + const val CTA_DISMISSED_STATE = "cta_dismissed" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt new file mode 100644 index 000000000000..a4ff6d3dfbef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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.communal.data.repository + +import dagger.Binds +import dagger.Module + +@Module +interface CommunalPrefsRepositoryModule { + @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt index 553b3ebc0813..1f4be4060223 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt @@ -56,9 +56,6 @@ interface CommunalRepository { /** Exposes the transition state of the communal [SceneTransitionLayout]. */ val transitionState: StateFlow<ObservableCommunalTransitionState> - /** Whether the CTA tile is visible in the hub under view mode. */ - val isCtaTileInViewModeVisible: Flow<Boolean> - /** Updates the requested scene. */ fun setDesiredScene(desiredScene: CommunalSceneKey) @@ -68,9 +65,6 @@ interface CommunalRepository { * Note that you must call is with `null` when the UI is done or risk a memory leak. */ fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) - - /** Updates whether to display the CTA tile in the hub under view mode. */ - fun setCtaTileInViewModeVisibility(isVisible: Boolean) } @OptIn(ExperimentalCoroutinesApi::class) @@ -102,16 +96,6 @@ constructor( initialValue = defaultTransitionState, ) - // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again - // once dismissed. - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } - override fun setDesiredScene(desiredScene: CommunalSceneKey) { _desiredScene.value = desiredScene } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e6816e954b5d..bfc5019801d0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -16,7 +16,6 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Intent @@ -29,6 +28,7 @@ import com.android.systemui.communal.data.db.CommunalWidgetDao import com.android.systemui.communal.data.db.CommunalWidgetItem import com.android.systemui.communal.shared.CommunalWidgetHost import com.android.systemui.communal.shared.model.CommunalWidgetContentModel +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -83,7 +83,7 @@ class CommunalWidgetRepositoryImpl @Inject constructor( private val appWidgetManager: Optional<AppWidgetManager>, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, broadcastDispatcher: BroadcastDispatcher, diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt index d0d9e3fabc7b..52f42c1aba4f 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt @@ -17,11 +17,11 @@ package com.android.systemui.communal.data.repository -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.content.Context import android.content.res.Resources import com.android.systemui.communal.shared.CommunalWidgetHost +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -48,15 +48,15 @@ interface CommunalWidgetRepositoryModule { @SysUISingleton @Provides - fun provideAppWidgetHost(@Application context: Context): AppWidgetHost { - return AppWidgetHost(context, APP_WIDGET_HOST_ID) + fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost { + return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID) } @SysUISingleton @Provides fun provideCommunalWidgetHost( appWidgetManager: Optional<AppWidgetManager>, - appWidgetHost: AppWidgetHost, + appWidgetHost: CommunalAppWidgetHost, @CommunalLog logBuffer: LogBuffer, ): CommunalWidgetHost { return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9fa4cd6c7985..aa4a9d0d9569 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -17,9 +17,9 @@ package com.android.systemui.communal.domain.interactor import android.app.smartspace.SmartspaceTarget -import android.appwidget.AppWidgetHost import android.content.ComponentName import com.android.systemui.communal.data.repository.CommunalMediaRepository +import com.android.systemui.communal.data.repository.CommunalPrefsRepository import com.android.systemui.communal.data.repository.CommunalRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel @@ -29,6 +29,7 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.HALF import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -49,10 +50,11 @@ class CommunalInteractor constructor( private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, + private val communalPrefsRepository: CommunalPrefsRepository, mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter ) { @@ -122,7 +124,7 @@ constructor( } /** Dismiss the CTA tile from the hub in view mode. */ - fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false) + suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser() /** * Add a widget at the specified position. @@ -174,8 +176,8 @@ constructor( /** CTA tile to be displayed in the glanceable hub (view mode). */ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> = - communalRepository.isCtaTileInViewModeVisible.map { visible -> - if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList() + communalPrefsRepository.isCtaDismissed.map { isDismissed -> + if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode()) } /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index 46f957f3aaf2..0d52afd4fff5 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.communal.domain.model -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetProviderInfo import android.widget.RemoteViews import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import java.util.UUID /** Encapsulates data for a communal content. */ @@ -44,7 +44,7 @@ sealed interface CommunalContentModel { class Widget( val appWidgetId: Int, val providerInfo: AppWidgetProviderInfo, - val appWidgetHost: AppWidgetHost, + val appWidgetHost: CommunalAppWidgetHost, ) : CommunalContentModel { override val key = KEY.widget(appWidgetId) // Widget size is always half. diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt index 41f9cb4c98ed..7fe37ccf0dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt @@ -16,11 +16,11 @@ package com.android.systemui.communal.shared -import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -35,7 +35,7 @@ class CommunalWidgetHost @Inject constructor( private val appWidgetManager: Optional<AppWidgetManager>, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @CommunalLog logBuffer: LogBuffer, ) { companion object { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index fcad45f950dc..317dd4040e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -20,7 +20,6 @@ import android.app.Activity import android.app.Activity.RESULT_CANCELED import android.app.Activity.RESULT_OK import android.app.ActivityOptions -import android.appwidget.AppWidgetHost import android.content.ActivityNotFoundException import android.content.ComponentName import android.widget.RemoteViews @@ -28,6 +27,7 @@ import com.android.internal.logging.UiEventLogger import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.log.CommunalUiEvent +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.MediaHost import com.android.systemui.media.dagger.MediaModule @@ -48,7 +48,7 @@ class CommunalEditModeViewModel @Inject constructor( private val communalInteractor: CommunalInteractor, - private val appWidgetHost: AppWidgetHost, + private val appWidgetHost: CommunalAppWidgetHost, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, private val uiEventLogger: UiEventLogger, ) : BaseCommunalViewModel(communalInteractor, mediaHost) { diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 09c18ed0c8de..d619362b0311 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -86,9 +86,11 @@ constructor( override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor() override fun onDismissCtaTile() { - communalInteractor.dismissCtaTile() - setPopupOnDismissCtaVisibility(true) - schedulePopupHiding() + scope.launch { + communalInteractor.dismissCtaTile() + setPopupOnDismissCtaVisibility(true) + schedulePopupHiding() + } } override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt new file mode 100644 index 000000000000..003c9d50e789 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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.communal.widgets + +import android.appwidget.AppWidgetHost +import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetProviderInfo +import android.content.Context + +/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */ +class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) { + override fun onCreateView( + context: Context, + appWidgetId: Int, + appWidget: AppWidgetProviderInfo? + ): AppWidgetHostView { + return CommunalAppWidgetHostView(context) + } + + /** + * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as + * `createView`. The only difference is that the returned value will be casted to + * [CommunalAppWidgetHostView]. + */ + fun createViewForCommunal( + context: Context?, + appWidgetId: Int, + appWidget: AppWidgetProviderInfo? + ): CommunalAppWidgetHostView { + // `createView` internally calls `onCreateView` to create the view. We cannot override + // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView` + return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt new file mode 100644 index 000000000000..2b7d82395b89 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 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.communal.widgets + +import android.appwidget.AppWidgetHostView +import android.content.Context +import android.graphics.Outline +import android.graphics.Rect +import android.view.View +import android.view.ViewOutlineProvider + +/** AppWidgetHostView that displays in communal hub with support for rounded corners. */ +class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) { + // Mutable corner radius. + var enforcedCornerRadius: Float + + // Mutable `Rect`. The size will be mutated when the widget is reapplied. + var enforcedRectangle: Rect + + init { + enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context) + enforcedRectangle = Rect() + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + super.onLayout(changed, l, t, r, b) + + enforceRoundedCorners() + } + + private val cornerRadiusEnforcementOutline: ViewOutlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View?, outline: Outline) { + if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) { + outline.setEmpty() + } else { + outline.setRoundRect(enforcedRectangle, enforcedCornerRadius) + } + } + } + + private fun enforceRoundedCorners() { + if (enforcedCornerRadius <= 0) { + resetRoundedCorners() + return + } + val background: View? = RoundedCornerEnforcement.findBackground(this) + if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) { + resetRoundedCorners() + return + } + RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle) + outlineProvider = cornerRadiusEnforcementOutline + clipToOutline = true + invalidateOutline() + } + + private fun resetRoundedCorners() { + outlineProvider = ViewOutlineProvider.BACKGROUND + clipToOutline = false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt new file mode 100644 index 000000000000..abda44be09fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 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.communal.widgets + +import android.annotation.IdRes +import android.annotation.Nullable +import android.content.Context +import android.content.res.Resources +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import androidx.core.os.BuildCompat.isAtLeastS +import com.android.systemui.res.R +import kotlin.math.min + +/** + * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the + * Launcher3 source code to enforce the same visual treatment on communal hub. + */ +internal object RoundedCornerEnforcement { + /** + * Find the background view for a widget. + * + * @param appWidget the view containing the App Widget (typically the instance of + * [CommunalAppWidgetHostView]). + */ + fun findBackground(appWidget: View): View? { + val backgrounds = findViewsWithId(appWidget, R.id.background) + if (backgrounds.size == 1) { + return backgrounds[0] + } + // Really, the argument should contain the widget, so it cannot be the background. + if (appWidget is ViewGroup) { + val vg = appWidget + if (vg.childCount > 0) { + return findUndefinedBackground(vg.getChildAt(0)) + } + } + return appWidget + } + + /** Check whether the app widget has opted out of the enforcement. */ + fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean { + return background.id == R.id.background && background.clipToOutline + } + + /** + * Computes the rounded rectangle needed for this app widget. + * + * @param appWidget View onto which the rounded rectangle will be applied. + * @param background Background view. This must be either `appWidget` or a descendant of + * `appWidget`. + * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of + * `appWidget`. + */ + fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) { + var background = background + outRect.left = 0 + outRect.right = background.width + outRect.top = 0 + outRect.bottom = background.height + while (background !== appWidget) { + outRect.offset(background.left, background.top) + background = background.parent as View + } + } + + /** Get the radius of the rounded rectangle defined in the host's resource. */ + private fun getOwnedEnforcedRadius(context: Context): Float { + val res: Resources = context.resources + return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) + } + + /** + * Computes the radius of the rounded rectangle that should be applied to a widget expanded in + * the given context. + */ + fun computeEnforcedRadius(context: Context): Float { + if (!isAtLeastS()) { + return 0f + } + val res: Resources = context.resources + val systemRadius: Float = + res.getDimension(android.R.dimen.system_app_widget_background_radius) + val defaultRadius = getOwnedEnforcedRadius(context) + return min(defaultRadius, systemRadius) + } + + private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> { + val output: MutableList<View> = ArrayList() + accumulateViewsWithId(view, viewId, output) + return output + } + + // Traverse views. If the predicate returns true, continue on the children, otherwise, don't. + private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) { + if (view.id == viewId) { + output.add(view) + return + } + if (view is ViewGroup) { + val vg = view + for (i in 0 until vg.childCount) { + accumulateViewsWithId(vg.getChildAt(i), viewId, output) + } + } + } + + private fun isViewVisible(view: View): Boolean { + return if (view.visibility != View.VISIBLE) { + false + } else !view.willNotDraw() || view.foreground != null || view.background != null + } + + @Nullable + private fun findUndefinedBackground(current: View): View? { + if (current.visibility != View.VISIBLE) { + return null + } + if (isViewVisible(current)) { + return current + } + var lastVisibleView: View? = null + // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw + // something, or a ViewGroup that contains more than one view. + if (current is ViewGroup) { + val vg = current + for (i in 0 until vg.childCount) { + val visibleView = findUndefinedBackground(vg.getChildAt(i)) + if (visibleView != null) { + if (lastVisibleView != null) { + return current // At least two visible children + } + lastVisibleView = visibleView + } + } + } + return lastVisibleView + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt index 496c64e1120e..c6fb4f9d6956 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt @@ -19,6 +19,8 @@ package com.android.systemui.keyboard import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl import dagger.Binds import dagger.Module @@ -27,4 +29,9 @@ abstract class KeyboardModule { @Binds abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository + + @Binds + abstract fun bindStickyKeysRepository( + repository: StickyKeysRepositoryImpl + ): StickyKeysRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt new file mode 100644 index 000000000000..37034f63aca7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 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.keyboard.stickykeys + +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.KeyboardLog +import javax.inject.Inject + +private const val TAG = "stickyKeys" + +class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) { + fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { str1 = linkedHashMap.toString() }, + { "new sticky keys state received: $str1" } + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt new file mode 100644 index 000000000000..34d288815570 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 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.keyboard.stickykeys.data.repository + +import android.hardware.input.InputManager +import android.hardware.input.InputManager.StickyModifierStateListener +import android.hardware.input.StickyModifierState +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +interface StickyKeysRepository { + val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> + val settingEnabled: Flow<Boolean> +} + +class StickyKeysRepositoryImpl +@Inject +constructor( + private val inputManager: InputManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val stickyKeysLogger: StickyKeysLogger, +) : StickyKeysRepository { + + override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> = + conflatedCallbackFlow { + val listener = StickyModifierStateListener { stickyModifierState -> + trySendWithFailureLogging(stickyModifierState, TAG) + } + // after registering, InputManager calls listener with the current value + inputManager.registerStickyModifierStateListener(Runnable::run, listener) + awaitClose { inputManager.unregisterStickyModifierStateListener(listener) } + } + .map { toStickyKeysMap(it) } + .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) } + .flowOn(backgroundDispatcher) + + // TODO(b/319837892): Implement reading actual setting + override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true) + + private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> { + val keys = linkedMapOf<ModifierKey, Locked>() + state.apply { + if (isAltGrModifierOn) keys[ALT_GR] = Locked(false) + if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true) + if (isAltModifierOn) keys[ALT] = Locked(false) + if (isAltModifierLocked) keys[ALT] = Locked(true) + if (isCtrlModifierOn) keys[CTRL] = Locked(false) + if (isCtrlModifierLocked) keys[CTRL] = Locked(true) + if (isMetaModifierOn) keys[META] = Locked(false) + if (isMetaModifierLocked) keys[META] = Locked(true) + if (isShiftModifierOn) keys[SHIFT] = Locked(false) + if (isShiftModifierLocked) keys[SHIFT] = Locked(true) + } + return keys + } + + companion object { + const val TAG = "StickyKeysRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt new file mode 100644 index 000000000000..d5f082a2566f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 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.keyboard.stickykeys.shared.model + +@JvmInline +value class Locked(val locked: Boolean) + +enum class ModifierKey(val text: String) { + ALT("ALT LEFT"), + ALT_GR("ALT RIGHT"), + CTRL("CTRL"), + META("META"), + SHIFT("SHIFT"), +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt new file mode 100644 index 000000000000..26eb706da200 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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.keyboard.stickykeys.ui.viewmodel + +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +class StickyKeysIndicatorViewModel +@Inject +constructor( + stickyKeysRepository: StickyKeysRepository, + keyboardRepository: KeyboardRepository, + @Application applicationScope: CoroutineScope, +) { + + @OptIn(ExperimentalCoroutinesApi::class) + val indicatorContent: Flow<Map<ModifierKey, Locked>> = + keyboardRepository.isAnyKeyboardConnected + .flatMapLatest { keyboardPresent -> + if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false) + } + .flatMapLatest { enabled -> + if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap()) + } + .stateIn(applicationScope, SharingStarted.Lazily, emptyMap()) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt index b373f8520254..fede47957a7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt @@ -53,14 +53,14 @@ interface KeyguardFaceAuthModule { @SysUISingleton @FaceAuthTableLog fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer { - return factory.create("FaceAuthTableLog", 100) + return factory.create("FaceAuthTableLog", 400) } @Provides @SysUISingleton @FaceDetectTableLog fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer { - return factory.create("FaceDetectTableLog", 100) + return factory.create("FaceDetectTableLog", 400) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index fedd63be1454..8fa33ee7d0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -54,12 +54,33 @@ constructor( ) { override fun start() { - listenForAodToLockscreenOrOccluded() + listenForAodToLockscreen() + listenForAodToPrimaryBouncer() listenForAodToGone() + listenForAodToOccluded() listenForTransitionToCamera(scope, keyguardInteractor) } - private fun listenForAodToLockscreenOrOccluded() { + /** + * There are cases where the transition to AOD begins but never completes, such as tapping power + * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to + * run AOD->OCCLUDED. + */ + private fun listenForAodToOccluded() { + scope.launch { + keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { + (isOccluded, startedKeyguardState) -> + if (isOccluded && startedKeyguardState == KeyguardState.AOD) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + } + } + } + } + + private fun listenForAodToLockscreen() { scope.launch { keyguardInteractor .dozeTransitionTo(DozeStateModel.FINISH) @@ -72,20 +93,15 @@ constructor( ::toTriple ) .collect { (_, lastStartedStep, occluded) -> - if (lastStartedStep.to == KeyguardState.AOD) { - val toState = - if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN + if (lastStartedStep.to == KeyguardState.AOD && !occluded) { val modeOnCanceled = - if ( - toState == KeyguardState.LOCKSCREEN && - lastStartedStep.from == KeyguardState.LOCKSCREEN - ) { + if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { TransitionModeOnCanceled.REVERSE } else { TransitionModeOnCanceled.LAST_VALUE } startTransitionTo( - toState = toState, + toState = KeyguardState.LOCKSCREEN, modeOnCanceled = modeOnCanceled, ) } @@ -93,11 +109,26 @@ constructor( } } + /** + * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the + * PRIMARY_BOUNCER. + */ + private fun listenForAodToPrimaryBouncer() { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample(startedKeyguardTransitionStep, ::Pair) + .collect { (isBouncerShowing, lastStartedTransitionStep) -> + if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) { + startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + } + } + } + } + private fun listenForAodToGone() { scope.launch { keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect { - pair -> - val (biometricUnlockState, keyguardState) = pair + (biometricUnlockState, keyguardState) -> if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) { startTransitionTo(KeyguardState.GONE) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 2a68f26d3ae7..0c0eb8a673a4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.LEFT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT +import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder @@ -96,6 +97,11 @@ constructor( constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin) connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) + + // The constraint set visibility for start and end button are default visible, set to + // ignore so the view's own initial visibility (invisible) is used + setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE) + setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE) } } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt new file mode 100644 index 000000000000..5910701d9f2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 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 javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class KeyboardLog 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 24cb8fff9b67..3e0094081638 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -603,6 +603,14 @@ public class LogModule { return factory.create("BluetoothTileDialogLog", 50); } + /** Provides a {@link LogBuffer} for the keyboard functionalities. */ + @Provides + @SysUISingleton + @KeyboardLog + public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) { + return factory.create("KeyboardLog", 50); + } + /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 0d641ac9c688..58e042868607 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -624,7 +624,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } if (!mIsEnabled) { - mGestureNavigationSettingsObserver.unregister(); + mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::unregister); if (DEBUG_MISSING_GESTURE) { Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener"); } @@ -642,7 +642,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } } else { - mGestureNavigationSettingsObserver.register(); + mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register); updateDisplaySize(); if (DEBUG_MISSING_GESTURE) { Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index aff4a6759a47..2077d733172d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -88,9 +88,9 @@ class FooterActionsViewModel( /** Called when the expansion of the Quick Settings changed. */ fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) { if (isInSplitShade) { - // In split shade, we want to fade in the background only at the very end (see - // b/240563302). - val delay = 0.99f + // In split shade, we want to fade in the background when the QS background starts to + // show. + val delay = 0.15f _alpha.value = expansion _backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay) } else { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index a6cccf1cb41b..e2959fe834d4 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -24,7 +24,9 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.statusbar.NotificationPresenter +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.init.NotificationsController +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.policy.HeadsUpManager import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -44,6 +46,7 @@ constructor( private val keyguardRepository: KeyguardRepository, private val headsUpManager: HeadsUpManager, private val powerInteractor: PowerInteractor, + private val activeNotificationsInteractor: ActiveNotificationsInteractor, ) : CoreStartable { private var notificationPresenter: NotificationPresenter? = null @@ -117,6 +120,14 @@ constructor( return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) { 1 } else { + getActiveNotificationsCount() + } + } + + private fun getActiveNotificationsCount(): Int { + return if (NotificationsLiveDataStoreRefactor.isEnabled) { + activeNotificationsInteractor.allNotificationsCountValue + } else { notificationsController?.getActiveNotificationsCount() ?: 0 } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index ab08f660789c..a755805d1872 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -37,6 +37,7 @@ object SceneContainerFlag { /** The flag description -- not an aconfig flag name */ const val DESCRIPTION = "SceneContainerFlag" + @JvmStatic inline val isEnabled get() = SCENE_CONTAINER_ENABLED && // mainStaticFlag diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 4a839b8df9ba..93cfc5dbcbe3 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -85,12 +85,13 @@ object SceneWindowRootViewBinder { view.addView( ComposeFacade.createSceneContainerView( - scope = this, - context = view.context, - viewModel = viewModel, - windowInsets = windowInsets, - sceneByKey = sortedSceneByKey, - ) + scope = this, + context = view.context, + viewModel = viewModel, + windowInsets = windowInsets, + sceneByKey = sortedSceneByKey, + ) + .also { it.id = R.id.scene_container_root_composable } ) val legacyView = view.requireViewById<View>(R.id.legacy_window_root) diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt new file mode 100644 index 000000000000..b09bfe21e014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 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.settings + +import android.annotation.UserIdInt +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Extension functions for [UserFileManager]. */ +object UserFileManagerExt { + + /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */ + fun UserFileManager.observeSharedPreferences( + fileName: String, + @Context.PreferencesMode mode: Int, + @UserIdInt userId: Int + ): Flow<Unit> = conflatedCallbackFlow { + val sharedPrefs = getSharedPreferences(fileName, mode, userId) + + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) } + + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index d0da945ca7d0..9af2d58910b6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -20,15 +20,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.media.controls.pipeline.MediaDataManager -import com.android.systemui.media.controls.ui.MediaHierarchyManager -import com.android.systemui.media.controls.ui.MediaHost -import com.android.systemui.media.controls.ui.MediaHostState -import com.android.systemui.media.dagger.MediaModule import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import javax.inject.Inject -import javax.inject.Named import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -46,7 +41,6 @@ constructor( val shadeHeaderViewModel: ShadeHeaderViewModel, val notifications: NotificationsPlaceholderViewModel, val mediaDataManager: MediaDataManager, - @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost, ) { /** The key of the scene we should switch to when swiping up. */ val upDestinationSceneKey: StateFlow<SceneKey> = @@ -83,12 +77,6 @@ constructor( } } - init { - mediaHost.expansion = MediaHostState.EXPANDED - mediaHost.showsOnlyActiveMedia = true - mediaHost.init(MediaHierarchyManager.LOCATION_QQS) - } - fun isMediaVisible(): Boolean { // TODO(b/296122467): handle updates to carousel visibility while scene is still visible return mediaDataManager.hasActiveMediaOrRecommendation() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 5180bab60fdf..b22e9fd2fb17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -68,7 +68,8 @@ constructor( /** * The same as [allNotificationsCount], but without flows, for easy access in synchronous code. */ - val allNotificationsCountValue: Int = repository.activeNotifications.value.individuals.size + val allNotificationsCountValue: Int + get() = repository.activeNotifications.value.individuals.size /** Are any notifications being actively presented in the notification stack? */ val areAnyNotificationsPresent: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index ac499601b962..1677418c5c30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.init import android.service.notification.StatusBarNotification import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlags import com.android.systemui.people.widget.PeopleSpaceWidgetManager import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.NotificationListener @@ -72,7 +71,6 @@ constructor( private val animatedImageNotificationManager: AnimatedImageNotificationManager, private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager, private val bubblesOptional: Optional<Bubbles>, - private val featureFlags: FeatureFlags ) : NotificationsController { override fun initialize( @@ -136,5 +134,8 @@ constructor( } } - override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value + override fun getActiveNotificationsCount(): Int { + NotificationsLiveDataStoreRefactor.assertInLegacyMode() + return notifLiveDataStore.activeNotifCount.value + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt new file mode 100644 index 000000000000..44fc77f8bd55 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the Async Group Header Inflation flag state. */ +@Suppress("NOTHING_TO_INLINE") +object AsyncGroupHeaderViewInflation { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the async inflation of group header views enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationAsyncGroupHeaderInflation() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index d9c51089d5f8..c527bb537f19 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -26,9 +26,9 @@ import android.util.MathUtils; import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; +import com.android.systemui.res.R; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; 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 e791a6490e15..04db653282ac 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 @@ -94,6 +94,7 @@ import com.android.systemui.flags.RefactorFlag; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.TouchLogger; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; @@ -187,6 +188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private int mOverflingDistance; private float mMaxOverScroll; private boolean mIsBeingDragged; + private boolean mSendingTouchesToSceneFramework; private int mLastMotionY; private int mDownX; private int mActivePointerId = INVALID_POINTER; @@ -1509,7 +1511,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); - updateStackPosition(); + + // when scene framework is enabled, updateStackPosition is already called by + // updateTopPadding every time the stack moves, so skip it here to avoid flickering. + if (!SceneContainerFlag.isEnabled()) { + updateStackPosition(); + } if (!skipHeightUpdate) { mExpandedHeight = height; @@ -2450,6 +2457,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; + mController.setIntrinsicContentHeight(mIntrinsicContentHeight); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger @@ -3558,8 +3566,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean onTouchEvent(MotionEvent ev) { - if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) { - return true; + if (mTouchHandler != null) { + boolean touchHandled = mTouchHandler.onTouchEvent(ev); + if (SceneContainerFlag.isEnabled() || touchHandled) { + return touchHandled; + } } return super.onTouchEvent(ev); @@ -3567,6 +3578,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if (SceneContainerFlag.isEnabled() && mIsBeingDragged) { + if (!mSendingTouchesToSceneFramework) { + // if this is the first touch being sent to the scene framework, + // convert it into a synthetic DOWN event. + mSendingTouchesToSceneFramework = true; + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + mController.sendTouchToSceneFramework(downEvent); + downEvent.recycle(); + } else { + mController.sendTouchToSceneFramework(ev); + } + + if ( + ev.getActionMasked() == MotionEvent.ACTION_UP + || ev.getActionMasked() == MotionEvent.ACTION_CANCEL + ) { + setIsBeingDragged(false); + } + return false; + } return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev)); } @@ -3633,6 +3665,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return true; } + // If the scene framework is enabled, ignore all non-move gestures if we are currently + // dragging - they should be dispatched to the scene framework. Move gestures should be let + // through to determine if we are still dragging or not. + if ( + SceneContainerFlag.isEnabled() + && mIsBeingDragged + && action != MotionEvent.ACTION_MOVE + ) { + setIsBeingDragged(false); + return false; + } + switch (action) { case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0 || !isInContentBounds(ev)) { @@ -3676,6 +3720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } if (mIsBeingDragged) { + // Defer actual scrolling to the scene framework if enabled + if (SceneContainerFlag.isEnabled()) { + setIsBeingDragged(false); + return false; + } // Scroll to follow the motion event mLastMotionY = y; float scrollAmount; @@ -3770,9 +3819,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } protected boolean isInsideQsHeader(MotionEvent ev) { - if (mQsHeader == null) { - Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch"); - return false; + if (SceneContainerFlag.isEnabled()) { + return ev.getY() < mController.getPlaceholderTop(); } mQsHeader.getBoundsOnScreen(mQsHeaderBound); @@ -4026,9 +4074,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestDisallowInterceptTouchEvent(true); cancelLongPress(); resetExposedMenuView(true /* animate */, true /* force */); + } else { + mSendingTouchesToSceneFramework = false; } } + @VisibleForTesting + boolean getIsBeingDragged() { + return mIsBeingDragged; + } + public void requestDisallowLongPress() { cancelLongPress(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index c0e0b73c750d..6a66bb74f16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -80,6 +80,9 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; @@ -120,6 +123,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -145,6 +149,7 @@ import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Provider; /** * Controller for {@link NotificationStackScrollLayout}. @@ -181,6 +186,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationRemoteInputManager mRemoteInputManager; private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; + private final Provider<WindowRootView> mWindowRootView; + private final NotificationStackAppearanceInteractor mStackAppearanceInteractor; private final KeyguardMediaController mKeyguardMediaController; private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; @@ -689,6 +696,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, + SceneContainerFlags sceneContainerFlags, + Provider<WindowRootView> windowRootView, + NotificationStackAppearanceInteractor stackAppearanceInteractor, InteractionJankMonitor jankMonitor, StackStateLogger stackLogger, NotificationStackScrollLogger logger, @@ -739,6 +749,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; + mWindowRootView = windowRootView; + mStackAppearanceInteractor = stackAppearanceInteractor; mFeatureFlags = featureFlags; mNotificationTargetsHelper = notificationTargetsHelper; mSecureSettings = secureSettings; @@ -1076,6 +1088,28 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getIntrinsicContentHeight(); } + /** + * Dispatch a touch to the scene container framework. + * TODO(b/316965302): Replace findViewById to avoid DFS + */ + public void sendTouchToSceneFramework(MotionEvent ev) { + View sceneContainer = mWindowRootView.get() + .findViewById(R.id.scene_container_root_composable); + if (sceneContainer != null) { + sceneContainer.dispatchTouchEvent(ev); + } + } + + /** Get the y-coordinate of the top bound of the stack. */ + public float getPlaceholderTop() { + return mStackAppearanceInteractor.getStackBounds().getValue().getTop(); + } + + /** Set the intrinsic height of the stack content without additional padding. */ + public void setIntrinsicContentHeight(float intrinsicContentHeight) { + mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight); + } + public void setIntrinsicPadding(int intrinsicPadding) { mView.setIntrinsicPadding(intrinsicPadding); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index e78a694735e0..aac3c28a3426 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -30,4 +30,18 @@ class NotificationStackAppearanceRepository @Inject constructor() { /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp = MutableStateFlow(32f) + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = MutableStateFlow(0f) + + /** + * The y-coordinate in px of top of the contents of the notification stack. This value can be + * negative, if the stack is scrolled such that its top extends beyond the top edge of the + * screen. + */ + val contentTop = MutableStateFlow(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 61a4dfcbd201..1dfde09f3a85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -34,12 +34,32 @@ constructor( /** The bounds of the notification stack in the current scene. */ val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow() + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow() + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop = repository.contentTop.asStateFlow() + /** Sets the position of the notification stack in the current scene. */ fun setStackBounds(bounds: NotificationContainerBounds) { check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } repository.stackBounds.value = bounds } - /** The corner radius of the notification stack, in dp. */ - val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() + /** Sets the height of the contents of the notification stack. */ + fun setIntrinsicContentHeight(height: Float) { + repository.intrinsicContentHeight.value = height + } + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun setContentTop(startY: Float) { + repository.contentTop.value = startY + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index a9b542dcce2d..ed15f557fb39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlin.math.pow import kotlin.math.roundToInt import kotlinx.coroutines.launch @@ -43,24 +44,28 @@ object NotificationStackAppearanceViewBinder { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.stackBounds.collect { bounds -> - controller.updateTopPadding( - bounds.top, - controller.isAddOrRemoveAnimationPending - ) controller.setRoundedClippingBounds( - it.left, - it.top, - it.right, - it.bottom, + bounds.left.roundToInt(), + bounds.top.roundToInt(), + bounds.right.roundToInt(), + bounds.bottom.roundToInt(), viewModel.cornerRadiusDp.value.dpToPx(context), viewModel.cornerRadiusDp.value.dpToPx(context), ) } } + + launch { + viewModel.contentTop.collect { + controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending) + } + } + launch { viewModel.expandFraction.collect { expandFraction -> ambientState.expansionFraction = expandFraction controller.expandedHeight = expandFraction * controller.view.height + controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f)) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index 834d3ffe63c9..74db5831f7f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -41,4 +41,7 @@ constructor( /** The corner radius of the notification stack, in dp. */ val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp + + /** The y-coordinate in px of top of the contents of the notification stack. */ + val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 9f22118e3332..385f0619288d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -21,9 +21,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.scene.shared.flag.SceneContainerFlags +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow /** @@ -35,6 +37,7 @@ class NotificationsPlaceholderViewModel @Inject constructor( private val interactor: NotificationStackAppearanceInteractor, + shadeInteractor: ShadeInteractor, flags: SceneContainerFlags, featureFlags: FeatureFlagsClassic, ) { @@ -66,4 +69,22 @@ constructor( /** The corner radius of the placeholder, in dp. */ val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp + + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val intrinsicContentHeight = interactor.intrinsicContentHeight + + /** + * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade + * is open. + */ + val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion + + /** Sets the y-coord in px of the top of the contents of the notification stack. */ + fun onContentTopChanged(padding: Float) { + interactor.setContentTop(padding) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index d8ef981b6a56..da6bfe84eee7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -48,10 +48,10 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -76,7 +76,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { protected MockitoSession mStaticMockSession; - protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); + protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; protected @Mock LockIconView mLockIconView; protected @Mock AnimatedStateListDrawable mIconDrawable; @@ -175,7 +175,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mPrimaryBouncerInteractor, mContext, () -> mDeviceEntryInteractor, - mSceneTestUtils.getFakeSceneContainerFlags() + mKosmos.getFakeSceneContainerFlags() ); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java index 132bdb55180a..b0887efed4d7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java @@ -373,7 +373,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_showBouncer_sceneContainerNotEnabled() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false); + mKosmos.getFakeSceneContainerFlags().setEnabled(false); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -387,7 +387,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_showBouncer() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true); + mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); // WHEN longPress @@ -401,7 +401,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest { @Test public void longPress_falsingTriggered_doesNotShowBouncer() { init(/* useMigrationFlag= */ false); - mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true); + mKosmos.getFakeSceneContainerFlags().setEnabled(true); when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true); // WHEN longPress diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 68879a54cfe4..5e5273b779c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -55,8 +55,6 @@ import android.os.Build; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -120,10 +118,6 @@ public class MenuViewLayerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Spy private SysuiTestableContext mSpyContext = getContext(); @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt index 7c626a141a4a..e0c6bbad5635 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt @@ -44,6 +44,8 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeViewController import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.concurrency.FakeExecutor @@ -56,6 +58,7 @@ import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import org.junit.Before @@ -89,6 +92,9 @@ class BackActionInteractorTest : SysuiTestCase() { @Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher @Mock private lateinit var iStatusBarService: IStatusBarService @Mock private lateinit var headsUpManager: HeadsUpManager + private val activeNotificationsRepository = ActiveNotificationListRepository() + private val activeNotificationsInteractor = + ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher()) private val keyguardRepository = FakeKeyguardRepository() private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy { @@ -98,6 +104,7 @@ class BackActionInteractorTest : SysuiTestCase() { keyguardRepository, headsUpManager, powerInteractor, + activeNotificationsInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 0ee09390d03a..43f7c60721ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics import android.app.admin.DevicePolicyManager +import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager @@ -79,6 +80,8 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +private const val OP_PACKAGE_NAME = "biometric.testapp" + @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -109,6 +112,8 @@ open class AuthContainerViewTest : SysuiTestCase() { lateinit var authController: AuthController @Mock lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock + private lateinit var packageManager: PackageManager private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) @@ -134,6 +139,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) + private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) private var authContainer: TestAuthContainerView? = null @@ -156,6 +162,9 @@ open class AuthContainerViewTest : SysuiTestCase() { selectedUserInteractor, testScope.backgroundScope, ) + // Set up default logo icon + whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) + context.setMockPackageManager(packageManager) } @After @@ -533,6 +542,7 @@ open class AuthContainerViewTest : SysuiTestCase() { mPromptInfo = PromptInfo().apply { this.authenticators = authenticators } + mOpPackageName = OP_PACKAGE_NAME }, testScope.backgroundScope, fingerprintProps, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index ec7ce634fd78..b39e09df9d2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -43,6 +43,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 9 private const val CHALLENGE = 90L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -102,7 +103,8 @@ class PromptRepositoryImplTest : SysuiTestCase() { PromptInfo().apply { isConfirmationRequested = case }, USER_ID, CHALLENGE, - PromptKind.Biometric() + PromptKind.Biometric(), + OP_PACKAGE_NAME ) assertThat(isConfirmationRequired).isEqualTo(case) @@ -120,7 +122,8 @@ class PromptRepositoryImplTest : SysuiTestCase() { PromptInfo().apply { isConfirmationRequested = case }, USER_ID, CHALLENGE, - PromptKind.Biometric() + PromptKind.Biometric(), + OP_PACKAGE_NAME ) assertThat(isConfirmationRequired).isTrue() @@ -133,17 +136,19 @@ class PromptRepositoryImplTest : SysuiTestCase() { val kind = PromptKind.Pin val promptInfo = PromptInfo() - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind) + repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME) assertThat(repository.kind.value).isEqualTo(kind) assertThat(repository.userId.value).isEqualTo(USER_ID) assertThat(repository.challenge.value).isEqualTo(CHALLENGE) assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo) + assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME) repository.unsetPrompt() assertThat(repository.promptInfo.value).isNull() assertThat(repository.userId.value).isNull() assertThat(repository.challenge.value).isNull() + assertThat(repository.opPackageName.value).isNull() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index dcefea28d4c8..8a46c0c6da9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -30,6 +30,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 22 private const val OPERATION_ID = 100L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -114,7 +115,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() { }, kind = kind, userId = USER_ID, - challenge = OPERATION_ID + challenge = OPERATION_ID, + opPackageName = OP_PACKAGE_NAME ) assertThat(prompt?.title).isEqualTo(title) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index f15b738f3e95..52b42750847a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -51,6 +51,7 @@ private const val NEGATIVE_TEXT = "escape" private const val USER_ID = 8 private const val CHALLENGE = 999L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -113,13 +114,20 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities) + interactor.useBiometricsForAuthentication( + info, + USER_ID, + CHALLENGE, + modalities, + OP_PACKAGE_NAME + ) assertThat(currentPrompt).isNotNull() assertThat(currentPrompt?.title).isEqualTo(TITLE) assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION) assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE) assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT) + assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME) if (allowCredentialFallback) { assertThat(credentialKind).isSameInstanceAs(PromptKind.Password) @@ -167,7 +175,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE) + interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME) // not using biometrics, should be null with no fallback option assertThat(currentPrompt).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt index a57b8905843c..a46167a423f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.biometrics.domain.model +import android.graphics.Bitmap import android.hardware.biometrics.PromptContentItemBulletedText import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest @@ -8,6 +9,7 @@ import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.promptInfo import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricUserInfo +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -15,6 +17,7 @@ import org.junit.runners.JUnit4 private const val USER_ID = 2 private const val OPERATION_ID = 8L +private const val OP_PACKAGE_NAME = "biometric.testapp" @SmallTest @RunWith(JUnit4::class) @@ -22,6 +25,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { @Test fun biometricRequestFromPromptInfo() { + val logoRes = R.drawable.ic_cake val title = "what" val subtitle = "a" val description = "request" @@ -36,6 +40,7 @@ class BiometricPromptRequestTest : SysuiTestCase() { val request = BiometricPromptRequest.Biometric( promptInfo( + logoRes = logoRes, title = title, subtitle = subtitle, description = description, @@ -44,8 +49,10 @@ class BiometricPromptRequestTest : SysuiTestCase() { BiometricUserInfo(USER_ID), BiometricOperationInfo(OPERATION_ID), BiometricModalities(fingerprintProperties = fpPros), + OP_PACKAGE_NAME, ) + assertThat(request.logoRes).isEqualTo(logoRes) assertThat(request.title).isEqualTo(title) assertThat(request.subtitle).isEqualTo(subtitle) assertThat(request.description).isEqualTo(description) @@ -57,6 +64,23 @@ class BiometricPromptRequestTest : SysuiTestCase() { } @Test + fun biometricRequestLogoBitmapFromPromptInfo() { + val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888) + val fpPros = fingerprintSensorPropertiesInternal().first() + val request = + BiometricPromptRequest.Biometric( + promptInfo( + logoBitmap = logoBitmap, + ), + BiometricUserInfo(USER_ID), + BiometricOperationInfo(OPERATION_ID), + BiometricModalities(fingerprintProperties = fpPros), + OP_PACKAGE_NAME, + ) + assertThat(request.logoBitmap).isEqualTo(logoBitmap) + } + + @Test fun credentialRequestFromPromptInfo() { val title = "what" val subtitle = "a" diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 394405440d7e..3888f2b940b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -16,8 +16,11 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.content.pm.PackageManager import android.content.res.Configuration +import android.graphics.Bitmap import android.graphics.Point +import android.graphics.drawable.BitmapDrawable import android.hardware.biometrics.PromptContentItemBulletedText import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo @@ -76,6 +79,7 @@ import org.mockito.junit.MockitoJUnit private const val USER_ID = 4 private const val CHALLENGE = 2L private const val DELAY = 1000L +private const val OP_PACKAGE_NAME = "biometric.testapp" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -88,9 +92,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Mock private lateinit var authController: AuthController @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils + @Mock private lateinit var packageManager: PackageManager private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() + private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) + private val logoResFromApp = R.drawable.ic_cake + private val logoFromApp = context.getDrawable(logoResFromApp) + private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565) private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository @@ -153,6 +162,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa udfpsUtils ) iconViewModel = viewModel.iconViewModel + + // Set up default logo icon and app customized icon + whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) + context.setMockPackageManager(packageManager) + val resources = context.getOrCreateTestableResources() + resources.addOverride(logoResFromApp, logoFromApp) } @Test @@ -1227,6 +1242,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa assertThat(contentView).isNull() } + @Test + fun defaultLogoIfNoLogoSet() = runGenericTest { + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isEqualTo(defaultLogoIcon) + } + + @Test + fun logoResSetByApp() = + runGenericTest(logoRes = logoResFromApp) { + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isEqualTo(logoFromApp) + } + + @Test + fun logoBitmapSetByApp() = + runGenericTest(logoBitmap = logoBitmapFromApp) { + val logo by collectLastValue(viewModel.logo) + assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) + } + /** Asserts that the selected buttons are visible now. */ private suspend fun TestScope.assertButtonsVisible( tryAgain: Boolean = false, @@ -1248,6 +1283,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa allowCredentialFallback: Boolean = false, description: String? = null, contentView: PromptContentView? = null, + logoRes: Int = -1, + logoBitmap: Bitmap? = null, block: suspend TestScope.() -> Unit ) { selector.initializePrompt( @@ -1257,6 +1294,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa face = testCase.face, descriptionFromApp = description, contentViewFromApp = contentView, + logoResFromApp = logoRes, + logoBitmapFromApp = logoBitmap, ) // put the view model in the initial authenticating state, unless explicitly skipped @@ -1434,9 +1473,13 @@ private fun PromptSelectorInteractor.initializePrompt( allowCredentialFallback: Boolean = false, descriptionFromApp: String? = null, contentViewFromApp: PromptContentView? = null, + logoResFromApp: Int = -1, + logoBitmapFromApp: Bitmap? = null, ) { val info = PromptInfo().apply { + logoRes = logoResFromApp + logoBitmap = logoBitmapFromApp title = "t" subtitle = "s" description = descriptionFromApp @@ -1445,11 +1488,13 @@ private fun PromptSelectorInteractor.initializePrompt( isDeviceCredentialAllowed = allowCredentialFallback isConfirmationRequested = requireConfirmation } + useBiometricsForAuthentication( info, USER_ID, CHALLENGE, BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), + OP_PACKAGE_NAME, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt index 44c57f34fa1b..134c40da1033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt @@ -3,8 +3,9 @@ package com.android.systemui.bouncer.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.SystemClock @@ -23,8 +24,8 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() { @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var bouncerLogger: TableLogBuffer - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope lateinit var underTest: KeyguardBouncerRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt new file mode 100644 index 000000000000..d397fc202637 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2024 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.keyboard.stickykeys.ui.viewmodel + +import android.hardware.input.InputManager +import android.hardware.input.StickyModifierState +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository +import com.android.systemui.keyboard.stickykeys.StickyKeysLogger +import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl +import com.android.systemui.keyboard.stickykeys.shared.model.Locked +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META +import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyZeroInteractions + +@SmallTest +@RunWith(JUnit4::class) +class StickyKeysIndicatorViewModelTest : SysuiTestCase() { + + private val dispatcher = StandardTestDispatcher() + private val testScope = TestScope(dispatcher) + private lateinit var viewModel: StickyKeysIndicatorViewModel + private val inputManager = mock<InputManager>() + private val keyboardRepository = FakeKeyboardRepository() + private val captor = + ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java) + + @Before + fun setup() { + val stickyKeysRepository = StickyKeysRepositoryImpl( + inputManager, + dispatcher, + mock<StickyKeysLogger>() + ) + viewModel = + StickyKeysIndicatorViewModel( + stickyKeysRepository = stickyKeysRepository, + keyboardRepository = keyboardRepository, + applicationScope = testScope.backgroundScope, + ) + } + + @Test + fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + runCurrent() + verifyZeroInteractions(inputManager) + + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + verify(inputManager) + .registerStickyModifierStateListener( + any(), + any(InputManager.StickyModifierStateListener::class.java) + ) + } + } + + @Test + fun stopsListeningToStickyKeysWhenKeyboardDisconnects() { + testScope.runTest { + collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + runCurrent() + + keyboardRepository.setIsAnyKeyboardConnected(false) + runCurrent() + + verify(inputManager).unregisterStickyModifierStateListener(any()) + } + } + + @Test + fun emitsStickyKeysListWhenStickyKeyIsPressed() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf(ALT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false))) + } + } + + @Test + fun emitsEmptyListWhenNoStickyKeysAreActive() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(emptyMap()) + + assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>()) + } + } + + @Test + fun passesAllStickyKeysToDialog() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + META to false, + SHIFT to false)) + + assertThat(stickyKeys).isEqualTo(mapOf( + ALT to Locked(false), + META to Locked(false), + SHIFT to Locked(false), + )) + } + } + + @Test + fun showsOnlyLockedStateIfKeyIsStickyAndLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + ALT to false, + ALT to true)) + + assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true))) + } + } + + @Test + fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() { + testScope.runTest { + val stickyKeys by collectLastValue(viewModel.indicatorContent) + keyboardRepository.setIsAnyKeyboardConnected(true) + + setStickyKeys(mapOf( + META to false, + SHIFT to false, // shift is sticky but not locked + CTRL to false)) + val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false)) + + setStickyKeys(mapOf( + SHIFT to false, + SHIFT to true, // shift is now locked + META to false, + CTRL to false)) + assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true))) + .isEqualTo(previousShiftIndex) + } + } + + private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) { + runCurrent() + verify(inputManager).registerStickyModifierStateListener(any(), captor.capture()) + captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys)) + runCurrent() + } + + private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) : + StickyModifierState() { + + private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value } + private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value } + + override fun isAltGrModifierLocked() = isLocked(ALT_GR) + override fun isAltGrModifierOn() = isOn(ALT_GR) + override fun isAltModifierLocked() = isLocked(ALT) + override fun isAltModifierOn() = isOn(ALT) + override fun isCtrlModifierLocked() = isLocked(CTRL) + override fun isCtrlModifierOn() = isOn(CTRL) + override fun isMetaModifierLocked() = isLocked(META) + override fun isMetaModifierOn() = isOn(META) + override fun isShiftModifierLocked() = isLocked(SHIFT) + override fun isShiftModifierOn() = isOn(SHIFT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 0ea4e9f8d416..8b6611f339c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -1342,14 +1342,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) runCurrent() - // WHEN the keyguard is occluded and aod ends + // WHEN the keyguard is occluded keyguardRepository.setKeyguardOccluded(true) - keyguardRepository.setDozeTransitionModel( - DozeTransitionModel( - from = DozeStateModel.DOZE_AOD, - to = DozeStateModel.FINISH, - ) - ) runCurrent() val info = @@ -1366,6 +1360,30 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun aodToPrimaryBouncer() = + testScope.runTest { + // GIVEN a prior transition has run to AOD + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD) + runCurrent() + + // WHEN the primary bouncer is set to show + bouncerRepository.setPrimaryShow(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.AOD) + assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToOccluded_fromCameraGesture() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index c7118066a276..1cb3bf6c7100 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -393,23 +393,23 @@ class FooterActionsViewModelTest : SysuiTestCase() { } @Test - fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() { + fun backgroundAlpha_inSplitShade_followsExpansion_with_0_15_delay() { val underTest = utils.footerActionsViewModel() val floatTolerance = 0.01f underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true) assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) - underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true) + underTest.onQuickSettingsExpansionChanged(0.1f, isInSplitShade = true) assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) - underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true) + underTest.onQuickSettingsExpansionChanged(0.14f, isInSplitShade = true) assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) - underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true) + underTest.onQuickSettingsExpansionChanged(0.235f, isInSplitShade = true) assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f) - underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true) + underTest.onQuickSettingsExpansionChanged(0.575f, isInSplitShade = true) assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f) underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 49579f6f46b4..b3e386e69905 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -113,6 +113,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransition import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.KeyguardMediaController; import com.android.systemui.media.controls.ui.MediaHierarchyManager; @@ -125,7 +126,6 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeAnimationRepository; @@ -355,8 +355,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; protected ShadeAnimationInteractor mShadeAnimationInteractor; - protected SceneTestUtils mUtils = new SceneTestUtils(this); - protected TestScope mTestScope = mUtils.getTestScope(); + protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + protected TestScope mTestScope = mKosmos.getTestScope(); protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 971c8a3d1c05..a894f877fe3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -73,11 +73,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.FakeWindowRootViewComponent; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -150,8 +150,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { private final Executor mMainExecutor = MoreExecutors.directExecutor(); private final Executor mBackgroundExecutor = MoreExecutors.directExecutor(); - private final SceneTestUtils mUtils = new SceneTestUtils(this); - private final TestScope mTestScope = mUtils.getTestScope(); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); private ShadeInteractor mShadeInteractor; private NotificationShadeWindowControllerImpl mNotificationShadeWindowController; @@ -178,15 +178,15 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeShadeRepository shadeRepository = new FakeShadeRepository(); - mScreenOffAnimationController = mUtils.getScreenOffAnimationController(); - mStatusBarStateController = spy(mUtils.getStatusBarStateController()); - PowerInteractor powerInteractor = mUtils.powerInteractor(); + mScreenOffAnimationController = mKosmos.getScreenOffAnimationController(); + mStatusBarStateController = spy(mKosmos.getStatusBarStateController()); + PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); SceneInteractor sceneInteractor = new SceneInteractor( mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -219,8 +219,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, shadeRepository, @@ -245,8 +245,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mKeyguardSecurityModel, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index ca68fd867b39..cbd4d2bfe377 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -61,6 +61,7 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -68,7 +69,6 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -131,8 +131,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { protected QuickSettingsController mQsController; - protected SceneTestUtils mUtils = new SceneTestUtils(this); - protected TestScope mTestScope = mUtils.getTestScope(); + protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + protected TestScope mTestScope = mKosmos.getTestScope(); @Mock protected Resources mResources; @Mock protected KeyguardBottomAreaView mQsFrame; @@ -203,8 +203,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); - mStatusBarStateController = mUtils.getStatusBarStateController(); - mInteractionJankMonitor = mUtils.getInteractionJankMonitor(); + mStatusBarStateController = mKosmos.getStatusBarStateController(); + mInteractionJankMonitor = mKosmos.getInteractionJankMonitor(); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); @@ -212,13 +212,13 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); - PowerInteractor powerInteractor = mUtils.powerInteractor(); + PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); SceneInteractor sceneInteractor = new SceneInteractor( mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -250,8 +250,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mShadeRepository, @@ -276,8 +276,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 215f8b1c462f..c4911a41b4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -33,6 +33,8 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.row.NotificationGutsManager import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -45,6 +47,7 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.test.StandardTestDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,6 +62,8 @@ import org.mockito.MockitoAnnotations @SmallTest class ShadeControllerImplTest : SysuiTestCase() { private val executor = FakeExecutor(FakeSystemClock()) + private val testDispatcher = StandardTestDispatcher() + private val activeNotificationsRepository = ActiveNotificationListRepository() @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var keyguardStateController: KeyguardStateController @@ -84,6 +89,7 @@ class ShadeControllerImplTest : SysuiTestCase() { FakeKeyguardRepository(), headsUpManager, PowerInteractorFactory.create().powerInteractor, + ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 4a365b9ab084..05e866e85112 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -41,10 +41,12 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -56,6 +58,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import kotlinx.coroutines.flow.emptyFlow import org.junit.Assert.assertEquals @@ -71,17 +74,17 @@ import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope - private val testDispatcher = utils.testDispatcher + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val testDispatcher = kosmos.testDispatcher private lateinit var shadeInteractor: ShadeInteractor private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor private lateinit var fromPrimaryBouncerTransitionInteractor: @@ -131,7 +134,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { FakeKeyguardBouncerRepository(), ConfigurationInteractor(configurationRepository), shadeRepository, - utils::sceneInteractor + { kosmos.sceneInteractor }, ) val keyguardTransitionInteractor = KeyguardTransitionInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 168e782d0481..ff02ef3d4e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -28,6 +28,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.Notification; import android.os.Handler; import android.os.Looper; @@ -56,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.logging.nano.Notifications; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -110,6 +114,11 @@ public class NotificationLoggerTest extends SysuiTestCase { private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); private final PowerInteractor mPowerInteractor = PowerInteractorFactory.create().getPowerInteractor(); + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); @@ -123,7 +132,8 @@ public class NotificationLoggerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor), mKeyguardRepository, mHeadsUpManager, - mPowerInteractor); + mPowerInteractor, + mActiveNotificationsInteractor); mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true); mEntry = new NotificationEntryBuilder() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 8a730cfd7ddd..71613edb8737 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -42,6 +42,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -85,6 +87,8 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository; +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -156,6 +160,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Mock private UserManager mUserManager; + private final ActiveNotificationListRepository mActiveNotificationListRepository = + new ActiveNotificationListRepository(); + private final ActiveNotificationsInteractor mActiveNotificationsInteractor = + new ActiveNotificationsInteractor(mActiveNotificationListRepository, + StandardTestDispatcher(null, null)); + private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; @Before @@ -171,7 +181,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { new WindowRootViewVisibilityRepository(mBarService, mExecutor), new FakeKeyguardRepository(), mHeadsUpManager, - PowerInteractorFactory.create().getPowerInteractor()); + PowerInteractorFactory.create().getPowerInteractor(), + mActiveNotificationsInteractor); mGutsManager = new NotificationGutsManager( mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 88662b60c7d1..89f826b2049d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -66,6 +66,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; +import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -91,6 +93,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; +import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor; import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.ScrimController; @@ -112,6 +115,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import javax.inject.Provider; + /** * Tests for {@link NotificationStackScrollLayoutController}. */ @@ -153,6 +158,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; + @Mock private SceneContainerFlags mSceneContainerFlags; + @Mock private Provider<WindowRootView> mWindowRootView; + @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor; @Mock private InteractionJankMonitor mJankMonitor; private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(), logcatLogBuffer()); @@ -724,6 +732,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mSeenNotificationsInteractor, mViewBinder, mShadeController, + mSceneContainerFlags, + mWindowRootView, + mNotificationStackAppearanceInteractor, mJankMonitor, mStackLogger, mLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a1721208b2f2..4afcc8c9da43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -20,6 +20,7 @@ import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION; +import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; @@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; @@ -51,6 +53,7 @@ import static org.mockito.Mockito.when; import android.graphics.Insets; import android.graphics.Rect; +import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; @@ -74,6 +77,7 @@ import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.EmptyShadeView; @@ -93,11 +97,13 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -947,6 +953,78 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { verify(runnable).run(); } + @Test + public void testDispatchTouchEvent_sceneContainerDisabled() { + Assume.assumeFalse(SceneContainerFlag.isEnabled()); + + MotionEvent event = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + + mStackScroller.dispatchTouchEvent(event); + + verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any()); + } + + @Test + public void testDispatchTouchEvent_sceneContainerEnabled() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent moveEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = moveEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(moveEvent); + + verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent); + } + + @Test + @EnableFlags(FLAG_SCENE_CONTAINER) + public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() { + Assume.assumeTrue(SceneContainerFlag.isEnabled()); + mStackScroller.setIsBeingDragged(true); + + MotionEvent upEvent = MotionEvent.obtain( + SystemClock.uptimeMillis(), + SystemClock.uptimeMillis(), + MotionEvent.ACTION_UP, + 0, + 0, + 0 + ); + MotionEvent syntheticDownEvent = upEvent.copy(); + syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(syntheticDownEvent))); + + mStackScroller.dispatchTouchEvent(upEvent); + + verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat( + new MotionEventMatcher(upEvent))); + assertFalse(mStackScroller.getIsBeingDragged()); + } + private void setBarStateForTest(int state) { // Can't inject this through the listener or we end up on the actual implementation // rather than the mock because the spy just coppied the anonymous inner /shruggie. @@ -993,4 +1071,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { /* metaState= */0 ); } + + private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> { + private final MotionEvent mLeftEvent; + + MotionEventMatcher(MotionEvent leftEvent) { + mLeftEvent = leftEvent; + } + + @Override + public boolean matches(MotionEvent right) { + return mLeftEvent.getActionMasked() == right.getActionMasked() + && mLeftEvent.getDownTime() == right.getDownTime() + && mLeftEvent.getEventTime() == right.getEventTime() + && mLeftEvent.getX() == right.getX() + && mLeftEvent.getY() == right.getY(); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index 91cbc3274900..7362e342f321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -24,9 +24,9 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.DevicePostureController @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.tuner.TunerService import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,8 +61,8 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class KeyguardBypassControllerTest : SysuiTestCase() { - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val featureFlags = FakeFeatureFlags() private val shadeRepository = FakeShadeRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 5fa7f13ecd6e..2d120cd9b13f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -60,10 +60,10 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.statusbar.CommandQueue; @@ -149,7 +149,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private final TestScope mTestScope = TestScopeProvider.getTestScope(); private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); - private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); private KeyguardInteractor mKeyguardInteractor; private KeyguardStatusBarViewModel mViewModel; @@ -166,11 +166,11 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mKeyguardRepository, mCommandQueue, PowerInteractorFactory.create().getPowerInteractor(), - mSceneTestUtils.getFakeSceneContainerFlags(), + mKosmos.getFakeSceneContainerFlags(), new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), - () -> mSceneTestUtils.sceneInteractor()); + () -> mKosmos.getSceneInteractor()); mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index 5f9c096b1f3c..ca0e526bbc30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -25,20 +25,22 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory -import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -47,20 +49,20 @@ import org.mockito.Mockito.verify @SmallTest @OptIn(ExperimentalCoroutinesApi::class) class KeyguardStatusBarViewModelTest : SysuiTestCase() { - private val testScope = TestScope() - private val sceneTestUtils = SceneTestUtils(this) + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private val keyguardRepository = FakeKeyguardRepository() private val keyguardInteractor = KeyguardInteractor( keyguardRepository, mock<CommandQueue>(), PowerInteractorFactory.create().powerInteractor, - sceneTestUtils.fakeSceneContainerFlags, + kosmos.fakeSceneContainerFlags, FakeKeyguardBouncerRepository(), ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), ) { - sceneTestUtils.sceneInteractor() + kosmos.sceneInteractor } private val keyguardStatusBarInteractor = KeyguardStatusBarInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index e6b9d9b09246..b6a033a7c5f6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -40,13 +40,18 @@ import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Text import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.res.R -import com.android.systemui.scene.SceneTestUtils import com.android.systemui.statusbar.policy.DeviceProvisionedController +import com.android.systemui.telephony.data.repository.fakeTelephonyRepository +import com.android.systemui.telephony.domain.interactor.telephonyInteractor +import com.android.systemui.testKosmos import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.source.UserRecord @@ -98,8 +103,8 @@ class UserSwitcherInteractorTest : SysuiTestCase() { @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - private val utils = SceneTestUtils(this) - private val testScope = utils.testScope + private val kosmos = testKosmos() + private val testScope = kosmos.testScope private lateinit var spyContext: Context private lateinit var userRepository: FakeUserRepository private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies @@ -121,16 +126,17 @@ class UserSwitcherInteractorTest : SysuiTestCase() { SUPERVISED_USER_CREATION_APP_PACKAGE, ) - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false) mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG) spyContext = spy(context) - keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags) + keyguardReply = + KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic) keyguardRepository = keyguardReply.repository userRepository = FakeUserRepository() refreshUsersScheduler = RefreshUsersScheduler( applicationScope = testScope.backgroundScope, - mainDispatcher = utils.testDispatcher, + mainDispatcher = kosmos.testDispatcher, repository = userRepository, ) } @@ -363,7 +369,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun actions_deviceUnlocked_fullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) @@ -447,7 +453,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 2, includeGuest = false) userRepository.setUserInfos(userInfos) userRepository.setSelectedUserInfo(userInfos[0]) @@ -640,7 +646,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { val refreshUsersCallCount = userRepository.refreshUsersCallCount - utils.telephonyRepository.setCallState(1) + kosmos.fakeTelephonyRepository.setCallState(1) runCurrent() assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) @@ -792,7 +798,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun userRecordsFullScreen() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val userInfos = createUserInfos(count = 3, includeGuest = false) userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) userRepository.setUserInfos(userInfos) @@ -901,7 +907,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() { createUserInteractor() testScope.runTest { - utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val expandable = mock<Expandable>() underTest.showUserSwitcher(expandable) @@ -1116,19 +1122,19 @@ class UserSwitcherInteractorTest : SysuiTestCase() { manager = manager, headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, - telephonyInteractor = utils.telephonyInteractor(), + telephonyInteractor = kosmos.telephonyInteractor, broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, - backgroundDispatcher = utils.testDispatcher, - mainDispatcher = utils.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, + mainDispatcher = kosmos.testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = GuestUserInteractor( applicationContext = spyContext, applicationScope = testScope.backgroundScope, - mainDispatcher = utils.testDispatcher, - backgroundDispatcher = utils.testDispatcher, + mainDispatcher = kosmos.testDispatcher, + backgroundDispatcher = kosmos.testDispatcher, manager = manager, repository = userRepository, deviceProvisionedController = deviceProvisionedController, @@ -1139,7 +1145,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() { resetOrExitSessionReceiver = resetOrExitSessionReceiver, ), uiEventLogger = uiEventLogger, - featureFlags = utils.fakeFeatureFlags, + featureFlags = kosmos.fakeFeatureFlagsClassic, userRestrictionChecker = mock(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 8920d4df2a91..1ed045ff6546 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -117,11 +117,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions; import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.scene.FakeWindowRootViewComponent; -import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; @@ -356,8 +356,8 @@ public class BubblesTest extends SysuiTestCase { @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper; - private final SceneTestUtils mUtils = new SceneTestUtils(this); - private final TestScope mTestScope = mUtils.getTestScope(); + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); private ShadeInteractor mShadeInteractor; private ShellTaskOrganizer mShellTaskOrganizer; private TaskViewTransitions mTaskViewTransitions; @@ -408,8 +408,8 @@ public class BubblesTest extends SysuiTestCase { FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); PowerInteractor powerInteractor = new PowerInteractor( - mUtils.getPowerRepository(), - mUtils.falsingCollector(), + mKosmos.getPowerRepository(), + mKosmos.getFalsingCollector(), mock(ScreenOffAnimationController.class), mStatusBarStateController); @@ -417,7 +417,7 @@ public class BubblesTest extends SysuiTestCase { mTestScope.getBackgroundScope(), new SceneContainerRepository( mTestScope.getBackgroundScope(), - mUtils.fakeSceneContainerConfig()), + mKosmos.getFakeSceneContainerConfig()), powerInteractor, mock(SceneLogger.class)); @@ -449,8 +449,8 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, shadeRepository, @@ -475,8 +475,8 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionRepository, keyguardTransitionInteractor, mTestScope.getBackgroundScope(), - mUtils.getTestDispatcher(), - mUtils.getTestDispatcher(), + mKosmos.getTestDispatcher(), + mKosmos.getTestDispatcher(), keyguardInteractor, featureFlags, mock(KeyguardSecurityModel.class), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index 42ec8fed0127..f192de23fecc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -26,12 +26,24 @@ class FakePromptRepository : PromptRepository { private val _isConfirmationRequired = MutableStateFlow(false) override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() + private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) + override val opPackageName = _opPackageName.asStateFlow() + override fun setPrompt( promptInfo: PromptInfo, userId: Int, gatekeeperChallenge: Long?, kind: PromptKind, - ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false) + opPackageName: String, + ) = + setPrompt( + promptInfo, + userId, + gatekeeperChallenge, + kind, + forceConfirmation = false, + opPackageName = opPackageName + ) fun setPrompt( promptInfo: PromptInfo, @@ -39,12 +51,14 @@ class FakePromptRepository : PromptRepository { gatekeeperChallenge: Long?, kind: PromptKind, forceConfirmation: Boolean = false, + opPackageName: String? = null, ) { _promptInfo.value = promptInfo _userId.value = userId _challenge.value = gatekeeperChallenge _kind.value = kind _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation + _opPackageName.value = opPackageName } override fun unsetPrompt() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt new file mode 100644 index 000000000000..79107cc818ba --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.communal.data.repository + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.communalPrefsRepository: CommunalPrefsRepository by + Kosmos.Fixture { fakeCommunalPrefsRepository } +val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt new file mode 100644 index 000000000000..d3ed58bf5be0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 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.communal.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Fake implementation of [CommunalPrefsRepository] */ +class FakeCommunalPrefsRepository : CommunalPrefsRepository { + private val _isCtaDismissed = MutableStateFlow(false) + override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow() + + override suspend fun setCtaDismissedForCurrentUser() { + _isCtaDismissed.value = true + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index e82cae45c8f0..c85c27e277b4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn @@ -53,12 +52,4 @@ class FakeCommunalRepository( fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) { _isCommunalHubShowing.value = isCommunalHubShowing } - - private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isCtaTileInViewModeVisible: Flow<Boolean> = - _isCtaTileInViewModeVisible.asStateFlow() - - override fun setCtaTileInViewModeVisibility(isVisible: Boolean) { - _isCtaTileInViewModeVisible.value = isVisible - } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt index 95ff889177b8..126bd6f03cca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt @@ -17,11 +17,12 @@ package com.android.systemui.communal.domain.interactor -import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository +import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalRepository import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -42,7 +43,8 @@ object CommunalInteractorFactory { mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(), smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(), tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(), - appWidgetHost: AppWidgetHost = mock(), + communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(), + appWidgetHost: CommunalAppWidgetHost = mock(), editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(), ): WithDependencies { val withDeps = @@ -55,6 +57,7 @@ object CommunalInteractorFactory { testScope, communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, tutorialRepository, @@ -66,6 +69,7 @@ object CommunalInteractorFactory { CommunalInteractor( communalRepository, widgetRepository, + communalPrefsRepository, mediaRepository, smartspaceRepository, withDeps.keyguardInteractor, @@ -79,13 +83,14 @@ object CommunalInteractorFactory { val testScope: TestScope, val communalRepository: FakeCommunalRepository, val widgetRepository: FakeCommunalWidgetRepository, + val communalPrefsRepository: FakeCommunalPrefsRepository, val mediaRepository: FakeCommunalMediaRepository, val smartspaceRepository: FakeSmartspaceRepository, val tutorialRepository: FakeCommunalTutorialRepository, val keyguardRepository: FakeKeyguardRepository, val keyguardInteractor: KeyguardInteractor, val tutorialInteractor: CommunalTutorialInteractor, - val appWidgetHost: AppWidgetHost, + val appWidgetHost: CommunalAppWidgetHost, val editWidgetsActivityStarter: EditWidgetsActivityStarter, val communalInteractor: CommunalInteractor, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 649b373258ef..7cbbaabe9dfa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.domain.interactor import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -30,6 +31,7 @@ val Kosmos.communalInteractor by Fixture { communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, appWidgetHost = mock(), keyguardInteractor = keyguardInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt index 59f0ec3cd3a5..ffca83bc6f08 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt @@ -16,11 +16,12 @@ package com.android.systemui.keyguard.domain.interactor -import android.appwidget.AppWidgetHost import com.android.systemui.communal.data.repository.communalMediaRepository +import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.widgets.CommunalAppWidgetHost import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.kosmos.Kosmos import com.android.systemui.smartspace.data.repository.smartspaceRepository @@ -32,9 +33,10 @@ val Kosmos.communalInteractor by communalRepository = communalRepository, widgetRepository = communalWidgetRepository, mediaRepository = communalMediaRepository, + communalPrefsRepository = communalPrefsRepository, smartspaceRepository = smartspaceRepository, keyguardInteractor = keyguardInteractor, - appWidgetHost = mock(AppWidgetHost::class.java), + appWidgetHost = mock(CommunalAppWidgetHost::class.java), editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt new file mode 100644 index 000000000000..24670b12193a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 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.kosmos + +import android.content.applicationContext +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.bouncerRepository +import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor +import com.android.systemui.classifier.falsingCollector +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.data.repository.fakePowerRepository +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.sceneContainerConfig +import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags +import com.android.systemui.statusbar.phone.screenOffAnimationController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository +import com.android.systemui.util.time.systemClock + +/** Helper for using [Kosmos] from Java. */ +@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.") +class KosmosJavaAdapter( + testCase: SysuiTestCase, +) { + + private val kosmos = Kosmos() + + val testDispatcher by lazy { kosmos.testDispatcher } + val testScope by lazy { kosmos.testScope } + val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } + val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } + val configurationRepository by lazy { kosmos.fakeConfigurationRepository } + val configurationInteractor by lazy { kosmos.configurationInteractor } + val bouncerRepository by lazy { kosmos.bouncerRepository } + val communalRepository by lazy { kosmos.fakeCommunalRepository } + val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + val powerRepository by lazy { kosmos.fakePowerRepository } + val clock by lazy { kosmos.systemClock } + val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository } + val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } + val statusBarStateController by lazy { kosmos.statusBarStateController } + val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } + val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } + val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig } + val sceneInteractor by lazy { kosmos.sceneInteractor } + val falsingCollector by lazy { kosmos.falsingCollector } + val powerInteractor by lazy { kosmos.powerInteractor } + + init { + kosmos.applicationContext = testCase.context + kosmos.testCase = testCase + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt deleted file mode 100644 index d314a25658e8..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2023 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.scene - -import android.content.Context -import android.content.applicationContext -import com.android.systemui.SysuiTestCase -import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository -import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor -import com.android.systemui.authentication.domain.interactor.authenticationInteractor -import com.android.systemui.bouncer.data.repository.bouncerRepository -import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository -import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor -import com.android.systemui.bouncer.domain.interactor.BouncerInteractor -import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor -import com.android.systemui.bouncer.domain.interactor.bouncerInteractor -import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor -import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel -import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel -import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.classifier.domain.interactor.FalsingInteractor -import com.android.systemui.classifier.domain.interactor.falsingInteractor -import com.android.systemui.classifier.falsingCollector -import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.configurationInteractor -import com.android.systemui.communal.data.repository.fakeCommunalRepository -import com.android.systemui.communal.domain.interactor.CommunalInteractor -import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor -import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.jank.interactionJankMonitor -import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.statusBarStateController -import com.android.systemui.power.data.repository.fakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.scene.data.repository.SceneContainerRepository -import com.android.systemui.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags -import com.android.systemui.scene.shared.model.SceneContainerConfig -import com.android.systemui.scene.shared.model.SceneKey -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel -import com.android.systemui.statusbar.phone.screenOffAnimationController -import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository -import com.android.systemui.telephony.data.repository.fakeTelephonyRepository -import com.android.systemui.telephony.domain.interactor.TelephonyInteractor -import com.android.systemui.telephony.domain.interactor.telephonyInteractor -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.user.domain.interactor.selectedUserInteractor -import com.android.systemui.util.time.systemClock -import kotlinx.coroutines.ExperimentalCoroutinesApi - -/** - * Utilities for creating scene container framework related repositories, interactors, and - * view-models for tests. - */ -@OptIn(ExperimentalCoroutinesApi::class) -@Deprecated("Please use Kosmos instead.") -class SceneTestUtils { - - val kosmos = Kosmos() - - constructor( - context: Context, - ) { - kosmos.applicationContext = context - } - - constructor(testCase: SysuiTestCase) : this(context = testCase.context) { - kosmos.testCase = testCase - } - - val testDispatcher by lazy { kosmos.testDispatcher } - val testScope by lazy { kosmos.testScope } - val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } - val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } - val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository } - val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository } - val configurationRepository by lazy { kosmos.fakeConfigurationRepository } - val configurationInteractor by lazy { kosmos.configurationInteractor } - val telephonyRepository by lazy { kosmos.fakeTelephonyRepository } - val bouncerRepository by lazy { kosmos.bouncerRepository } - val communalRepository by lazy { kosmos.fakeCommunalRepository } - val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } - val powerRepository by lazy { kosmos.fakePowerRepository } - val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository } - val clock by lazy { kosmos.systemClock } - val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository } - val simBouncerInteractor by lazy { kosmos.simBouncerInteractor } - val statusBarStateController by lazy { kosmos.statusBarStateController } - val interactionJankMonitor by lazy { kosmos.interactionJankMonitor } - val screenOffAnimationController by lazy { kosmos.screenOffAnimationController } - - fun fakeSceneContainerRepository(): SceneContainerRepository { - return kosmos.sceneContainerRepository - } - - fun fakeSceneKeys(): List<SceneKey> { - return kosmos.sceneKeys - } - - fun fakeSceneContainerConfig(): SceneContainerConfig { - return kosmos.sceneContainerConfig - } - - fun sceneInteractor(): SceneInteractor { - return kosmos.sceneInteractor - } - - fun deviceEntryInteractor(): DeviceEntryInteractor { - return kosmos.deviceEntryInteractor - } - - fun authenticationInteractor(): AuthenticationInteractor { - return kosmos.authenticationInteractor - } - - fun keyguardInteractor(): KeyguardInteractor { - return kosmos.keyguardInteractor - } - - fun communalInteractor(): CommunalInteractor { - return kosmos.communalInteractor - } - - fun bouncerInteractor(): BouncerInteractor { - return kosmos.bouncerInteractor - } - - fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel { - return kosmos.notificationsPlaceholderViewModel - } - - fun bouncerViewModel(): BouncerViewModel { - return kosmos.bouncerViewModel - } - - fun telephonyInteractor(): TelephonyInteractor { - return kosmos.telephonyInteractor - } - - fun falsingInteractor(): FalsingInteractor { - return kosmos.falsingInteractor - } - - fun falsingCollector(): FalsingCollector { - return kosmos.falsingCollector - } - - fun powerInteractor(): PowerInteractor { - return kosmos.powerInteractor - } - - fun selectedUserInteractor(): SelectedUserInteractor { - return kosmos.selectedUserInteractor - } - - fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor { - return kosmos.bouncerActionButtonInteractor - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index 0dbade76979c..d7e948eefc95 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -20,11 +20,13 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.scene.shared.flag.sceneContainerFlags +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationsPlaceholderViewModel by Fixture { NotificationsPlaceholderViewModel( interactor = notificationStackAppearanceInteractor, + shadeInteractor = shadeInteractor, flags = sceneContainerFlags, featureFlags = featureFlagsClassic, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt index d9beabb11ad9..d80ee758269f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor import com.android.systemui.statusbar.policy.headsUpManager val Kosmos.windowRootViewVisibilityInteractor by Fixture { @@ -32,5 +33,6 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture { keyguardRepository = keyguardRepository, headsUpManager = headsUpManager, powerInteractor = powerInteractor, + activeNotificationsInteractor = activeNotificationsInteractor, ) } diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig index 92e00ee0a477..727721d5319f 100644 --- a/services/autofill/features.aconfig +++ b/services/autofill/features.aconfig @@ -5,4 +5,11 @@ flag { namespace: "autofill" description: "Guards Autofill Framework against Autofill-Credman integration" bug: "296907283" +} + +flag { + name: "autofill_credman_integration_phase2" + namespace: "autofill" + description: "Guards against Autofill-Credman integration phase 2" + bug: "320730001" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 5a44ac803cb4..9d9e7c9345be 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -1387,6 +1387,8 @@ public final class BatteryService extends SystemService { case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE: case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE: case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY: + case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER: + case BatteryManager.BATTERY_PROPERTY_PART_STATUS: mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); break; diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index 329aac6f3a6a..9f279b1ba3fe 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -48,8 +48,6 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.am.DropboxRateLimiter; -import com.android.server.os.TombstoneProtos; -import com.android.server.os.TombstoneProtos.Tombstone; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -62,14 +60,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.attribute.PosixFilePermissions; -import java.util.AbstractMap; import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; /** * Performs a number of miscellaneous, non-system-critical actions @@ -337,12 +332,12 @@ public class BootReceiver extends BroadcastReceiver { * * @param ctx Context * @param tombstone path to the tombstone - * @param tombstoneProto the parsed proto tombstone + * @param proto whether the tombstone is stored as proto * @param processName the name of the process corresponding to the tombstone * @param tmpFileLock the lock for reading/writing tmp files */ public static void addTombstoneToDropBox( - Context ctx, File tombstone, Tombstone tombstoneProto, String processName, + Context ctx, File tombstone, boolean proto, String processName, ReentrantLock tmpFileLock) { final DropBoxManager db = ctx.getSystemService(DropBoxManager.class); if (db == null) { @@ -352,33 +347,31 @@ public class BootReceiver extends BroadcastReceiver { // Check if we should rate limit and abort early if needed. DropboxRateLimiter.RateLimitResult rateLimitResult = - sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName); + sDropboxRateLimiter.shouldRateLimit( + proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName); if (rateLimitResult.shouldRateLimit()) return; HashMap<String, Long> timestamps = readTimestamps(); try { - // Remove the memory data from the proto. - Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto); - - final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray(); - - // Use JNI to call the c++ proto to text converter and add the headers to the tombstone. - String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate()) - .append(rateLimitResult.createHeader()) - .append(getTombstoneText(tombstoneBytes)) - .toString(); - - // Add the tombstone without memory data to dropbox. - db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory); - - // Add the tombstone proto to dropbox. - if (recordFileTimestamp(tombstone, timestamps)) { - tmpFileLock.lock(); - try { - addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult); - } finally { - tmpFileLock.unlock(); + if (proto) { + if (recordFileTimestamp(tombstone, timestamps)) { + // We need to attach the count indicating the number of dropped dropbox entries + // due to rate limiting. Do this by enclosing the proto tombsstone in a + // container proto that has the dropped entry count and the proto tombstone as + // bytes (to avoid the complexity of reading and writing nested protos). + tmpFileLock.lock(); + try { + addAugmentedProtoToDropbox(tombstone, db, rateLimitResult); + } finally { + tmpFileLock.unlock(); + } } + } else { + // Add the header indicating how many events have been dropped due to rate limiting. + final String headers = getBootHeadersToLogAndUpdate() + + rateLimitResult.createHeader(); + addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE, + TAG_TOMBSTONE); } } catch (IOException e) { Slog.e(TAG, "Can't log tombstone", e); @@ -387,8 +380,11 @@ public class BootReceiver extends BroadcastReceiver { } private static void addAugmentedProtoToDropbox( - File tombstone, byte[] tombstoneBytes, DropBoxManager db, + File tombstone, DropBoxManager db, DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException { + // Read the proto tombstone file as bytes. + final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath()); + final File tombstoneProtoWithHeaders = File.createTempFile( tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR); Files.setPosixFilePermissions( @@ -421,8 +417,6 @@ public class BootReceiver extends BroadcastReceiver { } } - private static native String getTombstoneText(byte[] tombstoneBytes); - private static void addLastkToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String footers, String filename, int maxSize, @@ -440,31 +434,6 @@ public class BootReceiver extends BroadcastReceiver { addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag); } - /** Removes memory information from the Tombstone proto. */ - @VisibleForTesting - public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) { - Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder() - .clearMemoryMappings() - .clearThreads() - .putAllThreads(tombstoneProto.getThreadsMap().entrySet() - .stream() - .map(BootReceiver::clearMemoryDump) - .collect(Collectors.toMap(e->e.getKey(), e->e.getValue()))); - - if (tombstoneProto.hasSignalInfo()) { - tombstoneBuilder.setSignalInfo( - tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata()); - } - - return tombstoneBuilder.build(); - } - - private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump( - Map.Entry<Integer, TombstoneProtos.Thread> e) { - return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>( - e.getKey(), e.getValue().toBuilder().clearMemoryDump().build()); - } - private static void addFileToDropBox( DropBoxManager db, HashMap<String, Long> timestamps, String headers, String filename, int maxSize, String tag) throws IOException { diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 70bd4b328b43..c3916422159e 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -34,11 +34,11 @@ import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wm.SensitiveContentPackages.PackageInfo; import com.android.server.wm.WindowManagerInternal; -import java.util.Collections; import java.util.Set; /** @@ -54,6 +54,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic private @Nullable MediaProjectionManager mProjectionManager; private @Nullable WindowManagerInternal mWindowManager; + final Object mSensitiveContentProtectionLock = new Object(); + @GuardedBy("mSensitiveContentProtectionLock") + private boolean mProjectionActive = false; + private final MediaProjectionManager.Callback mProjectionCallback = new MediaProjectionManager.Callback() { @Override @@ -132,14 +136,23 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } private void onProjectionStart() { - StatusBarNotification[] notifications; - try { - notifications = mNotificationListener.getActiveNotifications(); - } catch (SecurityException e) { - Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e); - notifications = new StatusBarNotification[0]; + synchronized (mSensitiveContentProtectionLock) { + mProjectionActive = true; + updateAppsThatShouldBlockScreenCapture(); } + } + + private void onProjectionEnd() { + synchronized (mSensitiveContentProtectionLock) { + mProjectionActive = false; + + // notify windowmanager to clear any sensitive notifications observed during projection + // session + mWindowManager.clearBlockedApps(); + } + } + private void updateAppsThatShouldBlockScreenCapture() { RankingMap rankingMap; try { rankingMap = mNotificationListener.getCurrentRanking(); @@ -148,41 +161,98 @@ public final class SensitiveContentProtectionManagerService extends SystemServic rankingMap = null; } - // notify windowmanager of any currently posted sensitive content notifications - Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications( - notifications, - rankingMap); - - mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos); + updateAppsThatShouldBlockScreenCapture(rankingMap); } - private void onProjectionEnd() { - // notify windowmanager to clear any sensitive notifications observed during projection - // session - mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) { + StatusBarNotification[] notifications; + try { + notifications = mNotificationListener.getActiveNotifications(); + } catch (SecurityException e) { + Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e); + notifications = new StatusBarNotification[0]; + } + + // notify windowmanager of any currently posted sensitive content notifications + ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications( + notifications, rankingMap); + + mWindowManager.addBlockScreenCaptureForApps(packageInfos); } - private Set<PackageInfo> getSensitivePackagesFromNotifications( - StatusBarNotification[] notifications, RankingMap rankingMap) { + private ArraySet<PackageInfo> getSensitivePackagesFromNotifications( + @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) { + ArraySet<PackageInfo> sensitivePackages = new ArraySet<>(); if (rankingMap == null) { Log.w(TAG, "Ranking map not initialized."); - return Collections.emptySet(); + return sensitivePackages; } - Set<PackageInfo> sensitivePackages = new ArraySet<>(); for (StatusBarNotification sbn : notifications) { - NotificationListenerService.Ranking ranking = - rankingMap.getRawRankingObject(sbn.getKey()); - if (ranking != null && ranking.hasSensitiveContent()) { - PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid()); + PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap); + if (info != null) { sensitivePackages.add(info); } } return sensitivePackages; } - // TODO(b/317251408): add trigger that updates on onNotificationPosted, - // onNotificationRankingUpdate and onListenerConnected + private PackageInfo getSensitivePackageFromNotification(StatusBarNotification sbn, + RankingMap rankingMap) { + if (sbn == null) { + Log.w(TAG, "Unable to protect null notification"); + return null; + } + if (rankingMap == null) { + Log.w(TAG, "Ranking map not initialized."); + return null; + } + + NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey()); + if (ranking != null && ranking.hasSensitiveContent()) { + return new PackageInfo(sbn.getPackageName(), sbn.getUid()); + } + return null; + } + @VisibleForTesting - static class NotificationListener extends NotificationListenerService {} + class NotificationListener extends NotificationListenerService { + @Override + public void onListenerConnected() { + super.onListenerConnected(); + // Projection started before notification listener was connected + synchronized (mSensitiveContentProtectionLock) { + if (mProjectionActive) { + updateAppsThatShouldBlockScreenCapture(); + } + } + } + + @Override + public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { + super.onNotificationPosted(sbn, rankingMap); + synchronized (mSensitiveContentProtectionLock) { + if (!mProjectionActive) { + return; + } + + // notify windowmanager of any currently posted sensitive content notifications + PackageInfo packageInfo = getSensitivePackageFromNotification(sbn, rankingMap); + + if (packageInfo != null) { + mWindowManager.addBlockScreenCaptureForApps(new ArraySet(Set.of(packageInfo))); + } + } + } + + @Override + public void onNotificationRankingUpdate(RankingMap rankingMap) { + super.onNotificationRankingUpdate(rankingMap); + synchronized (mSensitiveContentProtectionLock) { + if (mProjectionActive) { + updateAppsThatShouldBlockScreenCapture(rankingMap); + } + } + } + } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 2b35231a51f8..ea1b0f5f66f7 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1076,6 +1076,7 @@ class StorageManagerService extends IStorageManager.Stub final UserManager userManager = mContext.getSystemService(UserManager.class); final List<UserInfo> users = userManager.getUsers(); + extendWatchdogTimeout("#onReset might be slow"); mStorageSessionController.onReset(mVold, () -> { mHandler.removeCallbacksAndMessages(null); }); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index f921b0b94b25..bd67cf42014a 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1126,6 +1126,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return; } + int phoneId = -1; + int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + if(Flags.preventSystemServerAndPhoneDeadlock()) { + // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, + // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + if (DBG) { + log("invalid subscription id, use default id"); + } + } else { //APP specify subID + subscriptionId = subId; + } + phoneId = getPhoneIdFromSubId(subscriptionId); + } + synchronized (mRecords) { // register IBinder b = callback.asBinder(); @@ -1145,17 +1160,23 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { r.renounceFineLocationAccess = renounceFineLocationAccess; r.callerUid = Binder.getCallingUid(); r.callerPid = Binder.getCallingPid(); - // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, - // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID - if (!SubscriptionManager.isValidSubscriptionId(subId)) { - if (DBG) { - log("invalid subscription id, use default id"); + + if(!Flags.preventSystemServerAndPhoneDeadlock()) { + // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID, + // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + if (DBG) { + log("invalid subscription id, use default id"); + } + r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; + } else {//APP specify subID + r.subId = subId; } - r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; - } else {//APP specify subID - r.subId = subId; + r.phoneId = getPhoneIdFromSubId(r.subId); + } else { + r.subId = subscriptionId; + r.phoneId = phoneId; } - r.phoneId = getPhoneIdFromSubId(r.subId); r.eventList = events; if (DBG) { @@ -1893,8 +1914,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) { + int phoneId = -1; + if(Flags.preventSystemServerAndPhoneDeadlock()) { + phoneId = getPhoneIdFromSubId(subId); + } synchronized (mRecords) { - int phoneId = getPhoneIdFromSubId(subId); + if(!Flags.preventSystemServerAndPhoneDeadlock()) { + phoneId = getPhoneIdFromSubId(subId); + } mCarrierNetworkChangeState[phoneId] = active; if (VDBG) { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 66a10e4a3c11..cd8be338a031 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -16,8 +16,10 @@ package com.android.server; +import static android.app.Flags.modesApi; import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE; import static android.app.UiModeManager.DEFAULT_PRIORITY; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; @@ -50,6 +52,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiModeManager; +import android.app.UiModeManager.AttentionModeThemeOverlayType; import android.app.UiModeManager.NightModeCustomReturnType; import android.app.UiModeManager.NightModeCustomType; import android.content.BroadcastReceiver; @@ -134,6 +137,7 @@ final class UiModeManagerService extends SystemService { private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED; private int mNightMode = UiModeManager.MODE_NIGHT_NO; private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; + private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0); private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0); private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME; @@ -839,6 +843,8 @@ final class UiModeManagerService extends SystemService { ? customModeType : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN; mNightMode = mode; + //deactivates AttentionMode if user toggles DarkTheme + mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF; resetNightModeOverrideLocked(); persistNightMode(user); // on screen off will update configuration instead @@ -879,6 +885,29 @@ final class UiModeManagerService extends SystemService { } } + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + @Override + public void setAttentionModeThemeOverlay( + @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { + setAttentionModeThemeOverlay_enforcePermission(); + + synchronized (mLock) { + if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) { + mAttentionModeThemeOverlay = attentionModeThemeOverlayType; + Binder.withCleanCallingIdentity(()-> updateLocked(0, 0)); + } + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + @Override + public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() { + getAttentionModeThemeOverlay_enforcePermission(); + synchronized (mLock) { + return mAttentionModeThemeOverlay; + } + } + @Override public void setApplicationNightMode(@UiModeManager.NightMode int mode) { switch (mode) { @@ -1406,7 +1435,7 @@ final class UiModeManagerService extends SystemService { pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") "); pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn); pw.print("/"); pw.print(mOverrideNightModeOff); - + pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay); pw.print(" mNightModeLocked="); pw.println(mNightModeLocked); pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled); @@ -1685,7 +1714,7 @@ final class UiModeManagerService extends SystemService { } @UiModeManager.NightMode - private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) { + private int getComputedUiModeConfiguration(int uiMode) { uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES : Configuration.UI_MODE_NIGHT_NO; uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO @@ -1980,18 +2009,26 @@ final class UiModeManagerService extends SystemService { } private void updateComputedNightModeLocked(boolean activate) { - mComputedNightMode = activate; - if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) { - return; - } - if (mOverrideNightModeOn && !mComputedNightMode) { - mComputedNightMode = true; - return; - } - if (mOverrideNightModeOff && mComputedNightMode) { - mComputedNightMode = false; - return; + boolean newComputedValue = activate; + if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) { + if (mOverrideNightModeOn && !newComputedValue) { + newComputedValue = true; + } else if (mOverrideNightModeOff && newComputedValue) { + newComputedValue = false; + } + } + + if (modesApi()) { + // Computes final night mode values based on Attention Mode. + mComputedNightMode = switch (mAttentionModeThemeOverlay) { + case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true; + case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false; + default -> newComputedValue; // case OFF + }; + } else { + mComputedNightMode = newComputedValue; } + if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null && mTwilightManager.getLastTwilightState() != null)) { resetNightModeOverrideLocked(); diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS new file mode 100644 index 000000000000..b18810564d88 --- /dev/null +++ b/services/core/java/com/android/server/adaptiveauth/OWNERS @@ -0,0 +1 @@ +hainingc@google.com
\ No newline at end of file diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 979449ffc179..9b1fade198fc 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5400,13 +5400,13 @@ public final class ActiveServices { return msg; } mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r, - hostingRecord, true); + true); if (isolated) { r.isolationHostProc = app; } } else { mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r, - hostingRecord, false); + false); } if (r.fgRequired) { diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index b90e5cd11216..c85723525aa1 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -81,18 +81,18 @@ public final class AppStartInfoTracker { private static final int FOREACH_ACTION_REMOVE_ITEM = 1; private static final int FOREACH_ACTION_STOP_ITERATION = 2; - private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; + @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16; @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore"; @VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo"; - private final Object mLock = new Object(); + @VisibleForTesting final Object mLock = new Object(); - private boolean mEnabled = false; + @VisibleForTesting boolean mEnabled = false; /** Initialized in {@link #init} and read-only after that. */ - private ActivityManagerService mService; + @VisibleForTesting ActivityManagerService mService; /** Initialized in {@link #init} and read-only after that. */ private Handler mHandler; @@ -112,7 +112,7 @@ public final class AppStartInfoTracker { * * <p>Initialized in {@link #init} and read-only after that. No lock is needed. */ - private int mAppStartInfoHistoryListSize; + @VisibleForTesting int mAppStartInfoHistoryListSize; @GuardedBy("mLock") private final ProcessMap<AppStartInfoContainer> mData; @@ -146,7 +146,8 @@ public final class AppStartInfoTracker { * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}. */ @GuardedBy("mLock") - private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>(); + @VisibleForTesting + final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>(); AppStartInfoTracker() { mCallbacks = new SparseArray<>(); @@ -229,7 +230,7 @@ public final class AppStartInfoTracker { ApplicationStartInfo info = mInProgRecords.get(id); info.setStartType((int) temperature); addBaseFieldsFromProcessRecord(info, app); - addStartInfoLocked(info); + mInProgRecords.put(id, addStartInfoLocked(info)); } else { mInProgRecords.remove(id); } @@ -262,6 +263,7 @@ public final class AppStartInfoTracker { ApplicationStartInfo info = mInProgRecords.get(id); info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); info.setLaunchMode(launchMode); + checkCompletenessAndCallback(info); } } @@ -281,7 +283,7 @@ public final class AppStartInfoTracker { } public void handleProcessServiceStart(long startTimeNs, ProcessRecord app, - ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) { + ServiceRecord serviceRecord, boolean cold) { synchronized (mLock) { if (!mEnabled) { return; @@ -297,7 +299,9 @@ public final class AppStartInfoTracker { && serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE") ? ApplicationStartInfo.START_REASON_JOB : ApplicationStartInfo.START_REASON_SERVICE); - start.setIntent(serviceRecord.intent.getIntent()); + if (serviceRecord.intent != null) { + start.setIntent(serviceRecord.intent.getIntent()); + } addStartInfoLocked(start); } } @@ -378,6 +382,7 @@ public final class AppStartInfoTracker { start.setPackageUid(app.info.uid); start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); start.setProcessName(app.processName); + start.setPackageName(app.info.packageName); } void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { @@ -419,12 +424,12 @@ public final class AppStartInfoTracker { } private void addTimestampToStart(ProcessRecord app, long timeNs, int key) { - addTimestampToStart(app.processName, app.uid, timeNs, key); + addTimestampToStart(app.info.packageName, app.uid, timeNs, key); } - private void addTimestampToStart(String processName, int uid, long timeNs, int key) { + private void addTimestampToStart(String packageName, int uid, long timeNs, int key) { synchronized (mLock) { - AppStartInfoContainer container = mData.get(processName, uid); + AppStartInfoContainer container = mData.get(packageName, uid); if (container == null) { // Record was not created, discard new data. return; @@ -443,11 +448,11 @@ public final class AppStartInfoTracker { final ApplicationStartInfo info = new ApplicationStartInfo(raw); - AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid()); + AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid()); if (container == null) { container = new AppStartInfoContainer(mAppStartInfoHistoryListSize); container.mUid = info.getRealUid(); - mData.put(raw.getProcessName(), raw.getRealUid(), container); + mData.put(raw.getPackageName(), raw.getRealUid(), container); } container.addStartInfoLocked(info); @@ -486,6 +491,9 @@ public final class AppStartInfoTracker { if (!mEnabled) { return; } + if (maxNum == 0) { + maxNum = APP_START_INFO_HISTORY_LIST_SIZE; + } final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -891,6 +899,7 @@ public final class AppStartInfoTracker { mProcStartInfoFile.delete(); } mData.getMap().clear(); + mInProgRecords.clear(); } } @@ -960,6 +969,10 @@ public final class AppStartInfoTracker { /** Convenience method to obtain timestamp of beginning of start.*/ private static long getStartTimestamp(ApplicationStartInfo startInfo) { + if (startInfo.getStartupTimestamps() == null + || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) { + return -1; + } return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH); } @@ -997,7 +1010,6 @@ public final class AppStartInfoTracker { if (oldestIndex >= 0) { mInfos.remove(oldestIndex); } - mInfos.remove(0); } mInfos.add(info); Collections.sort(mInfos, (a, b) -> diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index df8d9e1a406c..2ed217a89397 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -65,6 +65,9 @@ import static android.app.AppOpsManager.opRestrictsRead; import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; @@ -973,7 +976,29 @@ public class AppOpsService extends IAppOpsService.Stub { String pkgName = intent.getData().getEncodedSchemeSpecificPart(); int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); - if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { + if (action.equals(ACTION_PACKAGE_ADDED) + && !intent.getBooleanExtra(EXTRA_REPLACING, false)) { + PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName, + PackageManager.GET_PERMISSIONS, Process.myUid(), + UserHandle.getUserId(uid)); + boolean isSamplingTarget = isSamplingTarget(pi); + synchronized (AppOpsService.this) { + if (isSamplingTarget) { + mRarelyUsedPackages.add(pkgName); + } + UidState uidState = getUidStateLocked(uid, true); + if (!uidState.pkgOps.containsKey(pkgName)) { + uidState.pkgOps.put(pkgName, + new Ops(pkgName, uidState)); + } + + createSandboxUidStateIfNotExistsForAppLocked(uid); + } + } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { + synchronized (AppOpsService.this) { + packageRemovedLocked(uid, pkgName); + } + } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName); if (pkg == null) { return; @@ -1052,7 +1077,9 @@ public class AppOpsService extends IAppOpsService.Stub { mHistoricalRegistry.systemReady(mContext.getContentResolver()); IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED); packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED); packageUpdateFilter.addDataScheme("package"); mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, @@ -1079,7 +1106,7 @@ public class AppOpsService extends IAppOpsService.Stub { String action; if (!ArrayUtils.contains(pkgsInUid, pkg)) { - action = Intent.ACTION_PACKAGE_REMOVED; + action = ACTION_PACKAGE_REMOVED; } else { action = Intent.ACTION_PACKAGE_REPLACED; } @@ -1160,44 +1187,6 @@ public class AppOpsService extends IAppOpsService.Stub { // onUserRemoved handled by #removeUser }); - - getPackageManagerInternal().getPackageList( - new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int appId) { - PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, - PackageManager.GET_PERMISSIONS, Process.myUid(), - mContext.getUserId()); - boolean isSamplingTarget = isSamplingTarget(pi); - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - if (isSamplingTarget) { - mRarelyUsedPackages.add(packageName); - } - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - UidState uidState = getUidStateLocked(uid, true); - if (!uidState.pkgOps.containsKey(packageName)) { - uidState.pkgOps.put(packageName, - new Ops(packageName, uidState)); - } - - createSandboxUidStateIfNotExistsForAppLocked(uid); - } - } - } - - @Override - public void onPackageRemoved(String packageName, int appId) { - int[] userIds = getUserManagerInternal().getUserIds(); - synchronized (AppOpsService.this) { - for (int i = 0; i < userIds.length; i++) { - int uid = UserHandle.getUid(userIds[i], appId); - packageRemovedLocked(uid, packageName); - } - } - } - }); } /** @@ -2893,6 +2882,10 @@ public class AppOpsService extends IAppOpsService.Stub { return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, packageName); } + if (proxyAttributionTag != null + && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) { + proxyAttributionTag = null; + } synchronized (this) { final Ops ops = getOpsLocked(uid, packageName, attributionTag, @@ -3487,6 +3480,10 @@ public class AppOpsService extends IAppOpsService.Stub { return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, packageName); } + if (proxyAttributionTag != null + && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) { + proxyAttributionTag = null; + } boolean isRestricted = false; int startType = START_TYPE_FAILED; @@ -4340,6 +4337,36 @@ public class AppOpsService extends IAppOpsService.Stub { return false; } + /** + * Checks to see if the attribution tag is defined in either package or proxyPackage. + * This method is intended for ProxyAttributionTag validation and returns false + * if it does not exist in either one of them. + * + * @param packageName Name of the package + * @param proxyPackageName Name of the proxy package + * @param attributionTag attribution tag to be checked + * + * @return boolean specifying if attribution tag is valid or not + */ + private boolean isAttributionTagDefined(@Nullable String packageName, + @Nullable String proxyPackageName, + @Nullable String attributionTag) { + if (packageName == null) { + return false; + } else if (attributionTag == null) { + return true; + } + PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + if (proxyPackageName != null) { + AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName); + if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) { + return true; + } + } + AndroidPackage pkg = pmInt.getPackage(packageName); + return isAttributionInPackage(pkg, attributionTag); + } + private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e, @NonNull String methodName) { if (Process.isIsolated(uid)) { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index d5d8fd22314b..8fd2ee2bdc33 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -20,6 +20,7 @@ package com.android.server.biometrics; // TODO(b/141025588): Create separate internal and external permissions for AuthService. // TODO(b/141025588): Get rid of the USE_FINGERPRINT permission. +import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG; import static android.Manifest.permission.TEST_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; @@ -304,6 +305,9 @@ public class AuthService extends SystemService { if (promptInfo.containsPrivateApiConfigurations()) { checkInternalPermission(); } + if (promptInfo.containsManageBioApiConfigurations()) { + checkManageBiometricPermission(); + } final long identity = Binder.clearCallingIdentity(); try { @@ -984,6 +988,11 @@ public class AuthService extends SystemService { "Must have USE_BIOMETRIC_INTERNAL permission"); } + private void checkManageBiometricPermission() { + getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG, + "Must have MANAGE_BIOMETRIC_DIALOG permission"); + } + private void checkPermission() { if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING new file mode 100644 index 000000000000..ee4eeb634c84 --- /dev/null +++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "imports": [ + { + "path": "frameworks/base/core/tests/BroadcastRadioTests" + } + ] +} diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java index 1153cc37e3da..8a3a56cdc9ca 100644 --- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java +++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java @@ -16,6 +16,8 @@ package com.android.server.health; +import static android.os.Flags.batteryPartStatusApi; + import android.annotation.NonNull; import android.annotation.Nullable; import android.hardware.health.BatteryHealthData; @@ -150,6 +152,18 @@ class HealthServiceWrapperAidl extends HealthServiceWrapper { healthData = service.getBatteryHealthData(); prop.setLong(healthData.batteryStateOfHealth); break; + case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER: + if (batteryPartStatusApi()) { + healthData = service.getBatteryHealthData(); + prop.setString(healthData.batterySerialNumber); + } + break; + case BatteryManager.BATTERY_PROPERTY_PART_STATUS: + if (batteryPartStatusApi()) { + healthData = service.getBatteryHealthData(); + prop.setLong(healthData.batteryPartStatus); + } + break; } } catch (UnsupportedOperationException e) { // Leave prop untouched. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java index 295793569a25..a7632519b7bc 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java @@ -77,8 +77,6 @@ final class InputMethodInfoUtils { return this; } - // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be - // documented more clearly. InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { // If one or more auxiliary input methods are available, OK to stop populating the list. for (final InputMethodInfo imi : mInputMethodSet) { diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 4d19eade5a05..d7188c7f10c6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -42,7 +42,6 @@ import android.util.ArraySet; import android.util.Log; import android.util.Slog; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.am.ProcessList; @@ -414,7 +413,7 @@ public class NetworkPolicyLogger { private static final Date sDate = new Date(); public LogBuffer(int capacity) { - super(Data.class, capacity); + super(Data::new, Data[]::new, capacity); } public void uidStateChanged(int uid, int procState, long procStateSeq, @@ -690,12 +689,8 @@ public class NetworkPolicyLogger { /** * Container class for all networkpolicy events data. - * - * Note: This class needs to be public for RingBuffer class to be able to create - * new instances of this. */ - @Keep - public static final class Data { + private static final class Data { public int type; public long timeStamp; diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java index 71a6b5ed0581..ab650afe68a7 100644 --- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -16,7 +16,8 @@ package com.android.server.notification; -import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; import android.app.UiModeManager; import android.app.WallpaperManager; @@ -128,10 +129,9 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { private void updateNightModeImmediately(boolean useNightMode) { Binder.withCleanCallingIdentity(() -> { - // TODO: b/314285749 - Placeholder; use real APIs when available. - mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); - mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME, - useNightMode); + mUiModeManager.setAttentionModeThemeOverlay( + useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT + : MODE_ATTENTION_THEME_OVERLAY_OFF); }); } diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index 9ce3cb3abe4a..f6e7ef3d50e9 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -41,13 +41,14 @@ import android.system.Os; import android.system.StructStat; import android.util.Slog; import android.util.SparseArray; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoParseException; import com.android.internal.annotations.GuardedBy; import com.android.server.BootReceiver; import com.android.server.ServiceThread; import com.android.server.os.TombstoneProtos.Cause; import com.android.server.os.TombstoneProtos.Tombstone; -import com.android.server.os.protobuf.CodedInputStream; import libcore.io.IoUtils; @@ -129,21 +130,18 @@ public final class NativeTombstoneManager { return; } + String processName = "UNKNOWN"; final boolean isProtoFile = filename.endsWith(".pb"); - if (!isProtoFile) { - return; - } + File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb"); - Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true); + Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile); if (parsedTombstone.isPresent()) { - BootReceiver.addTombstoneToDropBox( - mContext, path, parsedTombstone.get().getTombstone(), - parsedTombstone.get().getProcessName(), mTmpFileLock); + processName = parsedTombstone.get().getProcessName(); } + BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock); } - private Optional<ParsedTombstone> handleProtoTombstone( - File path, boolean addToList) { + private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) { final String filename = path.getName(); if (!filename.endsWith(".pb")) { Slog.w(TAG, "unexpected tombstone name: " + path); @@ -173,7 +171,7 @@ public final class NativeTombstoneManager { return Optional.empty(); } - final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd); + final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd); if (!parsedTombstone.isPresent()) { IoUtils.closeQuietly(pfd); return Optional.empty(); @@ -186,7 +184,7 @@ public final class NativeTombstoneManager { previous.dispose(); } - mTombstones.put(number, parsedTombstone.get().getTombstoneFile()); + mTombstones.put(number, parsedTombstone.get()); } } @@ -334,27 +332,6 @@ public final class NativeTombstoneManager { } } - static class ParsedTombstone { - TombstoneFile mTombstoneFile; - Tombstone mTombstone; - ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) { - mTombstoneFile = tombstoneFile; - mTombstone = tombstone; - } - - public String getProcessName() { - return mTombstoneFile.getProcessName(); - } - - public TombstoneFile getTombstoneFile() { - return mTombstoneFile; - } - - public Tombstone getTombstone() { - return mTombstone; - } - } - static class TombstoneFile { final ParcelFileDescriptor mPfd; @@ -437,21 +414,67 @@ public final class NativeTombstoneManager { } } - static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) { - Tombstone tombstoneProto; - try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) { - final byte[] tombstoneBytes = is.readAllBytes(); + static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) { + final FileInputStream is = new FileInputStream(pfd.getFileDescriptor()); + final ProtoInputStream stream = new ProtoInputStream(is); + + int pid = 0; + int uid = 0; + String processName = null; + String crashReason = ""; + String selinuxLabel = ""; + + try { + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Tombstone.PID: + pid = stream.readInt(Tombstone.PID); + break; + + case (int) Tombstone.UID: + uid = stream.readInt(Tombstone.UID); + break; + + case (int) Tombstone.COMMAND_LINE: + if (processName == null) { + processName = stream.readString(Tombstone.COMMAND_LINE); + } + break; - tombstoneProto = Tombstone.parseFrom( - CodedInputStream.newInstance(tombstoneBytes)); - } catch (IOException ex) { + case (int) Tombstone.CAUSES: + if (!crashReason.equals("")) { + // Causes appear in decreasing order of likelihood. For now we only + // want the most likely crash reason here, so ignore all others. + break; + } + long token = stream.start(Tombstone.CAUSES); + cause: + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) Cause.HUMAN_READABLE: + crashReason = stream.readString(Cause.HUMAN_READABLE); + break cause; + + default: + break; + } + } + stream.end(token); + break; + + case (int) Tombstone.SELINUX_LABEL: + selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL); + break; + + default: + break; + } + } + } catch (IOException | ProtoParseException ex) { Slog.e(TAG, "Failed to parse tombstone", ex); return Optional.empty(); } - int pid = tombstoneProto.getPid(); - int uid = tombstoneProto.getUid(); - if (!UserHandle.isApp(uid)) { Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring"); return Optional.empty(); @@ -468,7 +491,6 @@ public final class NativeTombstoneManager { final int userId = UserHandle.getUserId(uid); final int appId = UserHandle.getAppId(uid); - String selinuxLabel = tombstoneProto.getSelinuxLabel(); if (!selinuxLabel.startsWith("u:r:untrusted_app")) { Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring"); return Optional.empty(); @@ -480,30 +502,11 @@ public final class NativeTombstoneManager { result.mAppId = appId; result.mPid = pid; result.mUid = uid; - result.mProcessName = getCmdLineProcessName(tombstoneProto); + result.mProcessName = processName == null ? "" : processName; result.mTimestampMs = timestampMs; - result.mCrashReason = getCrashReason(tombstoneProto); + result.mCrashReason = crashReason; - return Optional.of(new ParsedTombstone(result, tombstoneProto)); - } - - private static String getCmdLineProcessName(Tombstone tombstoneProto) { - for (String cmdline : tombstoneProto.getCommandLineList()) { - if (cmdline != null) { - return cmdline; - } - } - return ""; - } - - private static String getCrashReason(Tombstone tombstoneProto) { - for (Cause cause : tombstoneProto.getCausesList()) { - if (cause.getHumanReadable() != null - && !cause.getHumanReadable().equals("")) { - return cause.getHumanReadable(); - } - } - return ""; + return Optional.of(result); } public IParcelFileDescriptorRetriever getPfdRetriever() { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 991555495ad2..ac826afc1d22 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -32,6 +32,8 @@ import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; +import static com.android.server.pm.PackageArchiver.isArchivingEnabled; + import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; @@ -56,7 +58,6 @@ import android.content.IntentSender; import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; -import android.content.pm.Flags; import android.content.pm.ILauncherApps; import android.content.pm.IOnAppsChangedListener; import android.content.pm.IPackageInstallerCallback; @@ -507,7 +508,8 @@ public class LauncherAppsService extends SystemService { if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) { return false; } - if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) { + if (isArchivingEnabled() && packageName != null + && isPackageArchived(packageName, user)) { return true; } if (mPackageManagerInternal.filterAppAccess( @@ -530,7 +532,7 @@ public class LauncherAppsService extends SystemService { .addCategory(Intent.CATEGORY_LAUNCHER) .setPackage(packageName), user); - if (Flags.archiving()) { + if (isArchivingEnabled()) { launcherActivities = getActivitiesForArchivedApp(packageName, user, launcherActivities); } @@ -701,7 +703,7 @@ public class LauncherAppsService extends SystemService { callingUid, user.getIdentifier()); if (activityInfo == null) { - if (Flags.archiving()) { + if (isArchivingEnabled()) { return getMatchingArchivedAppActivityInfo(component, user); } return null; @@ -984,7 +986,7 @@ public class LauncherAppsService extends SystemService { long callingFlag = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - if (Flags.archiving()) { + if (isArchivingEnabled()) { callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES; } final PackageInfo info = @@ -1457,7 +1459,7 @@ public class LauncherAppsService extends SystemService { if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) { return false; } - if (Flags.archiving() && component != null && component.getPackageName() != null) { + if (isArchivingEnabled() && component != null && component.getPackageName() != null) { List<LauncherActivityInfoInternal> archiveActivities = generateLauncherActivitiesForArchivedApp(component.getPackageName(), user); if (!archiveActivities.isEmpty()) { @@ -1788,7 +1790,7 @@ public class LauncherAppsService extends SystemService { } if (!canLaunch && includeArchivedApps - && Flags.archiving() + && isArchivingEnabled() && getMatchingArchivedAppActivityInfo(component, user) != null) { launchIntent.setPackage(null); launchIntent.setComponent(component); diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index b18f2bf21a2b..09a91eda483a 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -53,11 +53,11 @@ import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedActivityParcel; import android.content.pm.ArchivedPackageInfo; import android.content.pm.ArchivedPackageParcel; +import android.content.pm.Flags; import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.content.pm.PackageManager.DeleteFlags; import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.VersionedPackage; @@ -78,6 +78,7 @@ import android.os.ParcelableException; import android.os.Process; import android.os.RemoteException; import android.os.SELinux; +import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ExceptionUtils; @@ -173,12 +174,15 @@ public class PackageArchiver { return userState.getArchiveState() != null && !userState.isInstalled(); } + public static boolean isArchivingEnabled() { + return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false); + } + void requestArchive( @NonNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender intentSender, - @NonNull UserHandle userHandle, - @DeleteFlags int flags) { + @NonNull UserHandle userHandle) { Objects.requireNonNull(packageName); Objects.requireNonNull(callerPackageName); Objects.requireNonNull(intentSender); @@ -218,7 +222,7 @@ public class PackageArchiver { new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST), callerPackageName, - DELETE_ARCHIVE | DELETE_KEEP_DATA | flags, + DELETE_ARCHIVE | DELETE_KEEP_DATA, intentSender, userId, binderUid); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index fdcd28b0ed02..7bf9fe7aa7e2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -30,6 +30,7 @@ import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; +import static com.android.server.pm.PackageArchiver.isArchivingEnabled; import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -826,7 +827,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE; - if (Flags.archiving() && params.appPackageName != null) { + if (isArchivingEnabled() && params.appPackageName != null) { PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal( params.appPackageName, SYSTEM_UID); if (ps != null @@ -1034,7 +1035,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private int getExistingDraftSessionIdInternal(int installerUid, SessionParams sessionParams, int userId) { String appPackageName = sessionParams.appPackageName; - if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) { + if (!isArchivingEnabled() || installerUid == INVALID_UID || appPackageName == null) { return SessionInfo.INVALID_ID; } @@ -1407,14 +1408,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, statusReceiver, versionedPackage.getPackageName(), canSilentlyInstallPackage, userId, mPackageArchiver, flags); - final boolean shouldShowConfirmationDialog = - (flags & PackageManager.DELETE_SHOW_DIALOG) != 0; - if (!shouldShowConfirmationDialog - && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) - == PackageManager.PERMISSION_GRANTED) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES) + == PackageManager.PERMISSION_GRANTED) { // Sweet, call straight through! mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); - } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) { + } else if (canSilentlyInstallPackage) { // Allow the device owner and affiliated profile owner to silently delete packages // Need to clear the calling identity to get DELETE_PACKAGES permission final long ident = Binder.clearCallingIdentity(); @@ -1656,10 +1654,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements @NonNull String packageName, @NonNull String callerPackageName, @NonNull IntentSender intentSender, - @NonNull UserHandle userHandle, - @DeleteFlags int flags) { - mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, - userHandle, flags); + @NonNull UserHandle userHandle) { + mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle); } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 5724ee0d94e9..ca00c84da724 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4645,7 +4645,7 @@ class PackageManagerShellCommand extends ShellCommand { try { mInterface.getPackageInstaller().requestArchive(packageName, /* callerPackageName= */ "", receiver.getIntentSender(), - new UserHandle(translatedUserId), 0); + new UserHandle(translatedUserId)); } catch (Exception e) { pw.println("Failure [" + e.getMessage() + "]"); return 1; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3e3b72caaf2e..70352be01096 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -392,8 +392,7 @@ final class RemovePackageHelper { // Delete from mSettings final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mPm.mLock) { - mPm.mSettings.removePackageLPw(packageName); - outInfo.mIsAppIdRemoved = true; + outInfo.mIsAppIdRemoved = mPm.mSettings.removePackageAndAppIdLPw(packageName); if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) { final SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs); // If we don't have a disabled system package to reinstall, the package is @@ -485,8 +484,6 @@ final class RemovePackageHelper { synchronized (mPm.mInstallLock) { cleanUpResourcesLI(codeFile, instructionSets); } - // TODO: open logging to help debug, will delete or add debug flag - Slog.d(TAG, "cleanUpResources for " + codeFile); if (packageName == null) { return; } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index b286b12dcf7d..a97652c8e167 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -92,7 +92,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; -import com.android.internal.pm.parsing.pkg.PackageImpl; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; import com.android.internal.pm.pkg.component.ParsedPermission; @@ -1460,22 +1459,28 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile return false; } - int removePackageLPw(String name) { + + /** + * Remove package from mPackages and its corresponding AppId. + * + * @return True if the AppId has been removed. + * False if the app doesn't exist, or if the app has a shared UID and there are other apps that + * still use the same shared UID even after the target app is removed. + */ + boolean removePackageAndAppIdLPw(String name) { final PackageSetting p = mPackages.remove(name); if (p != null) { removeInstallerPackageStatus(name); SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p); if (sharedUserSetting != null) { sharedUserSetting.removePackage(p); - if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) { - return sharedUserSetting.mAppId; - } + return checkAndPruneSharedUserLPw(sharedUserSetting, false); } else { removeAppIdLPw(p.getAppId()); - return p.getAppId(); + return true; } } - return -1; + return false; } /** diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index e993d9e5b724..d644235b8714 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; +import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.graphics.drawable.Icon; import android.os.Binder; import android.os.PersistableBundle; import android.os.StrictMode; +import android.os.SystemClock; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.ArraySet; @@ -192,6 +194,9 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; @GuardedBy("mLock") + private long mLastReportedTime; + + @GuardedBy("mLock") private boolean mIsAppSearchSchemaUpToDate; private ShortcutPackage(ShortcutUser shortcutUser, @@ -1673,6 +1678,26 @@ class ShortcutPackage extends ShortcutPackageItem { return condition[0]; } + void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal, + @NonNull final String shortcutId) { + synchronized (mLock) { + final long currentTS = SystemClock.elapsedRealtime(); + final ShortcutService s = mShortcutUser.mService; + if (currentTS - mLastReportedTime > s.mSaveDelayMillis) { + mLastReportedTime = currentTS; + } else { + return; + } + final long token = s.injectClearCallingIdentity(); + try { + usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId, + getPackageUserId()); + } finally { + s.injectRestoreCallingIdentity(token); + } + } + } + public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) { pw.println(); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 96c205c00ea9..c23d2abf0853 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -371,7 +371,7 @@ public class ShortcutService extends IShortcutService.Stub { private CompressFormat mIconPersistFormat; private int mIconPersistQuality; - private int mSaveDelayMillis; + int mSaveDelayMillis; private final IPackageManager mIPackageManager; private final PackageManagerInternal mPackageManagerInternal; @@ -2291,7 +2291,7 @@ public class ShortcutService extends IShortcutService.Stub { packageShortcutsChanged(ps, changedShortcuts, removedShortcuts); - reportShortcutUsedInternal(packageName, shortcut.getId(), userId); + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId()); verifyStates(); } @@ -2695,25 +2695,17 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d", shortcutId, packageName, userId)); } + final ShortcutPackage ps; synchronized (mLock) { throwIfUserLockedL(userId); - final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); + ps = getPackageShortcutsForPublisherLocked(packageName, userId); if (ps.findShortcutById(shortcutId) == null) { Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s", packageName, shortcutId)); return; } } - reportShortcutUsedInternal(packageName, shortcutId, userId); - } - - private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) { - final long token = injectClearCallingIdentity(); - try { - mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId); - } finally { - injectRestoreCallingIdentity(token); - } + ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId); } @Override @@ -5202,13 +5194,11 @@ public class ShortcutService extends IShortcutService.Stub { } // Injection point. - @VisibleForTesting long injectClearCallingIdentity() { return Binder.clearCallingIdentity(); } // Injection point. - @VisibleForTesting void injectRestoreCallingIdentity(long token) { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7b0a69bc6786..54055904d090 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static android.content.Intent.ACTION_SCREEN_OFF; +import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY; @@ -41,6 +43,7 @@ import android.annotation.ColorRes; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.StringRes; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -74,8 +77,10 @@ import android.content.pm.UserProperties; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.res.Configuration; import android.content.res.Resources; +import android.database.ContentObserver; import android.graphics.Bitmap; import android.multiuser.Flags; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -83,6 +88,7 @@ import android.os.Debug; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IProgressListener; import android.os.IUserManager; @@ -91,6 +97,7 @@ import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.PersistableBundle; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -174,6 +181,8 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -296,6 +305,12 @@ public class UserManagerService extends IUserManager.Stub { private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000; + /** + * The time duration (in milliseconds) post device inactivity after which the private space + * should be auto-locked if the corresponding settings option is selected by the user. + */ + private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; + // Tron counters private static final String TRON_GUEST_CREATED = "users_guest_created"; private static final String TRON_USER_CREATED = "users_user_created"; @@ -321,6 +336,8 @@ public class UserManagerService extends IUserManager.Stub { private final Handler mHandler; + private final ThreadPoolExecutor mInternalExecutor; + private final File mUsersDir; private final File mUserListFile; @@ -522,6 +539,36 @@ public class UserManagerService extends IUserManager.Stub { private final LockPatternUtils mLockPatternUtils; + private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener; + + /** Token to identify and remove already scheduled private space auto-lock messages */ + private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object(); + + /** Content observer to get callbacks for privte space autolock settings changes */ + private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver; + + private final class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (isAutoLockForPrivateSpaceEnabled()) { + final String path = uri.getLastPathSegment(); + if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) { + int autoLockPreference = + Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, + getMainUserIdUnchecked()); + Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference); + setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference); + } + } + } + } + private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK = "com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK"; @@ -534,12 +581,168 @@ public class UserManagerService extends IUserManager.Stub { final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class); final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); - // Call setQuietModeEnabled on bg thread to avoid ANR - BackgroundThread.getHandler().post(() -> - setQuietModeEnabled(userId, false, target, callingPackage)); + setQuietModeEnabledAsync(userId, false, target, callingPackage); } }; + /** Checks if the device inactivity broadcast receiver is already registered*/ + private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false; + + private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (isAutoLockForPrivateSpaceEnabled()) { + if (ACTION_SCREEN_OFF.equals(intent.getAction())) { + maybeScheduleMessageToAutoLockPrivateSpace(); + } else if (ACTION_SCREEN_ON.equals(intent.getAction())) { + // Remove any queued messages since the device is interactive again + mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN); + } + } + } + }; + + @VisibleForTesting + void maybeScheduleMessageToAutoLockPrivateSpace() { + // No action needed if auto-lock on inactivity not selected + int privateSpaceAutoLockPreference = + Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, + getMainUserIdUnchecked()); + if (privateSpaceAutoLockPreference + != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) { + return; + } + int privateProfileUserId = getPrivateProfileUserId(); + if (privateProfileUserId != UserHandle.USER_NULL) { + scheduleMessageToAutoLockPrivateSpace(privateProfileUserId, + PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN, + PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS); + } + } + + @VisibleForTesting + void scheduleMessageToAutoLockPrivateSpace(int userId, Object token, + long delayInMillis) { + mHandler.postDelayed(() -> { + final PowerManager powerManager = mContext.getSystemService(PowerManager.class); + if (powerManager != null && !powerManager.isInteractive()) { + Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId); + setQuietModeEnabledAsync(userId, true, + /* target */ null, mContext.getPackageName()); + } else { + Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock"); + } + }, token, delayInMillis); + } + + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + private void initializeAndRegisterKeyguardLockedStateListener() { + mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged; + // Register with keyguard to send locked state events to the listener initialized above + try { + final KeyguardManager keyguardManager = + mContext.getSystemService(KeyguardManager.class); + Slog.i(LOG_TAG, "Adding keyguard locked state listener"); + keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler), + mKeyguardLockedStateListener); + } catch (Exception e) { + Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e); + } + } + + @VisibleForTesting + @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) + void setOrUpdateAutoLockPreferenceForPrivateProfile( + @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) { + int privateProfileUserId = getPrivateProfileUserId(); + if (privateProfileUserId == UserHandle.USER_NULL) { + Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found"); + return; + } + + if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) { + // Register inactivity broadcast + if (!mIsDeviceInactivityBroadcastReceiverRegistered) { + Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers"); + mContext.registerReceiver(mDeviceInactivityBroadcastReceiver, + new IntentFilter(ACTION_SCREEN_OFF), + null, mHandler); + + mContext.registerReceiver(mDeviceInactivityBroadcastReceiver, + new IntentFilter(ACTION_SCREEN_ON), + null, mHandler); + + mIsDeviceInactivityBroadcastReceiverRegistered = true; + } + } else { + // Unregister device inactivity broadcasts + if (mIsDeviceInactivityBroadcastReceiverRegistered) { + Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers"); + mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN); + mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver); + mIsDeviceInactivityBroadcastReceiverRegistered = false; + } + } + + if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) { + // Initialize and add keyguard state listener + initializeAndRegisterKeyguardLockedStateListener(); + } else { + // Remove keyguard state listener + try { + final KeyguardManager keyguardManager = + mContext.getSystemService(KeyguardManager.class); + Slog.i(LOG_TAG, "Removing keyguard locked state listener"); + keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener); + } catch (Exception e) { + Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e); + } + } + } + + @VisibleForTesting + void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) { + if (isAutoLockForPrivateSpaceEnabled()) { + int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, + getMainUserIdUnchecked()); + boolean isAutoLockOnDeviceLockSelected = + autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK; + if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) { + int privateProfileUserId = getPrivateProfileUserId(); + if (privateProfileUserId != UserHandle.USER_NULL) { + Slog.i(LOG_TAG, "Auto-locking private space with user-id " + + privateProfileUserId); + setQuietModeEnabledAsync(privateProfileUserId, + /* enableQuietMode */true, /* target */ null, + mContext.getPackageName()); + } + } + } + } + + @VisibleForTesting + void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode, + IntentSender target, @Nullable String callingPackage) { + if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) { + // Call setQuietModeEnabled on a separate thread. Calling this operation on the main + // thread can cause ANRs, posting on a BackgroundThread can result in delays + Slog.d(LOG_TAG, "Calling setQuietModeEnabled for user " + userId + + " on a separate thread"); + mInternalExecutor.execute(() -> setQuietModeEnabled(userId, enableQuietMode, target, + callingPackage)); + } else { + // Call setQuietModeEnabled on bg thread to avoid ANR + BackgroundThread.getHandler().post( + () -> setQuietModeEnabled(userId, enableQuietMode, target, + callingPackage) + ); + } + } + /** * Cache the owner name string, since it could be read repeatedly on a critical code path * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place. @@ -766,6 +969,8 @@ public class UserManagerService extends IUserManager.Stub { mPackagesLock = packagesLock; mUsers = users != null ? users : new SparseArray<>(); mHandler = new MainHandler(); + mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1, + /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); mUserVisibilityMediator = new UserVisibilityMediator(mHandler); mUserDataPreparer = userDataPreparer; mUserTypes = UserTypeFactory.getUserTypes(); @@ -790,9 +995,15 @@ public class UserManagerService extends IUserManager.Stub { mLockPatternUtils = new LockPatternUtils(mContext); mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING); mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null; + mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler); emulateSystemUserModeIfNeeded(); } + private static boolean isAutoLockForPrivateSpaceEnabled() { + return android.os.Flags.allowPrivateProfile() + && Flags.supportAutolockForPrivateSpace(); + } + void systemReady() { mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -809,6 +1020,20 @@ public class UserManagerService extends IUserManager.Stub { new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, mHandler); + if (isAutoLockForPrivateSpaceEnabled()) { + + int mainUserId = getMainUserIdUnchecked(); + + mContext.getContentResolver().registerContentObserverAsUser(Settings.Secure.getUriFor( + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false, + mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId)); + + setOrUpdateAutoLockPreferenceForPrivateProfile( + Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId)); + } + markEphemeralUsersForRemoval(); } @@ -973,6 +1198,18 @@ public class UserManagerService extends IUserManager.Stub { return UserHandle.USER_NULL; } + private @UserIdInt int getPrivateProfileUserId() { + synchronized (mUsersLock) { + for (int userId : getUserIds()) { + UserInfo userInfo = getUserInfoLU(userId); + if (userInfo != null && userInfo.isPrivateProfile()) { + return userInfo.id; + } + } + } + return UserHandle.USER_NULL; + } + @Override public void setBootUser(@UserIdInt int userId) { checkCreateUsersPermission("Set boot user"); diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java index 375ef6150830..a4dbce6b2631 100644 --- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java +++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java @@ -65,6 +65,7 @@ import com.android.internal.widget.LockSettingsInternal; import com.android.internal.widget.RebootEscrowListener; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.Watchdog; import com.android.server.pm.ApexManager; import com.android.server.recoverysystem.hal.BootControlHIDL; @@ -112,6 +113,9 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo private static final int SOCKET_CONNECTION_MAX_RETRY = 30; + /** How long to pause the watchdog for when rebooting the device. */ + private static final int REBOOT_WATCHDOG_PAUSE_DURATION_MS = 20_000; + static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp"; static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count"; @@ -899,7 +903,8 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo // Clear the metrics prefs after a successful RoR reboot. mInjector.getMetricsPrefs().deletePrefsFile(); - + Watchdog.getInstance().pauseWatchingCurrentThreadFor( + REBOOT_WATCHDOG_PAUSE_DURATION_MS, "reboot can be slow"); PowerManager pm = mInjector.getPowerManager(); pm.reboot(reason); return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED; diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index d903ad4d9f0d..73fc8e9cfbc4 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2280,6 +2280,26 @@ public final class TvInputManagerService extends SystemService { } @Override + public void setVideoFrozen(IBinder sessionToken, boolean isFrozen, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "setVideoFrozen"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .setVideoFrozen(isFrozen); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in setVideoFrozen", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled, int userId) { final int callingUid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java index e3aba0f6bc6f..c15ac37565ff 100644 --- a/services/core/java/com/android/server/utils/AnrTimer.java +++ b/services/core/java/com/android/server/utils/AnrTimer.java @@ -111,7 +111,7 @@ public class AnrTimer<V> implements AutoCloseable { * but it can be changed for local testing. */ private static boolean anrTimerServiceEnabled() { - return Flags.anrTimerServiceEnabled(); + return Flags.anrTimerService(); } /** diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig index 489e21ab06ca..163116b9c5c7 100644 --- a/services/core/java/com/android/server/utils/flags.aconfig +++ b/services/core/java/com/android/server/utils/flags.aconfig @@ -1,7 +1,7 @@ package: "com.android.server.utils" flag { - name: "anr_timer_service_enabled" + name: "anr_timer_service" namespace: "system_performance" is_fixed_read_only: true description: "Feature flag for the ANR timer service" diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index 60dc4ff224bc..d95b431752ec 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -348,6 +348,10 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface { private void pinWebviewIfRequired(ApplicationInfo appInfo) { PinnerService pinnerService = LocalServices.getService(PinnerService.class); + if (pinnerService == null) { + // This happens in unit tests which do not have services. + return; + } int webviewPinQuota = pinnerService.getWebviewPinQuota(); if (webviewPinQuota <= 0) { return; diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index e7621ffe8e3c..182e1c133790 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -144,22 +145,20 @@ class ActivityStartInterceptor { } private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) { - Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary(); + ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary(); + activityOptions.setPendingIntentCreatorBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); final TaskFragment taskFragment = getLaunchTaskFragment(); // If the original intent is going to be embedded, try to forward the embedding TaskFragment // and its task id to embed back the original intent. if (taskFragment != null) { - ActivityOptions activityOptions = bOptions != null - ? ActivityOptions.fromBundle(bOptions) - : ActivityOptions.makeBasic(); activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken()); - bOptions = activityOptions.toBundle(); } final IIntentSender target = mService.getIntentSenderLocked( INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId, null /*token*/, null /*resultCode*/, 0 /*requestCode*/, new Intent[] { mIntent }, new String[] { mResolvedType }, - flags, bOptions); + flags, activityOptions.toBundle()); return new IntentSender(target); } @@ -272,12 +271,12 @@ class ActivityStartInterceptor { * * @return the activity option used to start the original intent. */ - private Bundle deferCrossProfileAppsAnimationIfNecessary() { + private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() { if (hasCrossProfileAnimation()) { mActivityOptions = null; - return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + return ActivityOptions.makeOpenCrossProfileAppsAnimation(); } - return null; + return ActivityOptions.makeBasic(); } private boolean interceptQuietProfileIfNeeded() { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 13f71521c240..f6d77ea33598 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -60,6 +60,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.server.pm.PackageArchiver.isArchivingEnabled; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; @@ -104,7 +105,6 @@ import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.AuxiliaryResolveInfo; -import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; @@ -1034,7 +1034,7 @@ class ActivityStarter { } if (err == ActivityManager.START_SUCCESS && aInfo == null) { - if (Flags.archiving()) { + if (isArchivingEnabled()) { PackageArchiver packageArchiver = mService .getPackageManagerInternalLocked() .getPackageArchiver(); diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java index 3862b82512c3..a7d6903bbe30 100644 --- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java +++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java @@ -21,7 +21,6 @@ import android.util.ArraySet; import java.io.PrintWriter; import java.util.Objects; -import java.util.Set; /** * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is @@ -41,10 +40,33 @@ public class SensitiveContentPackages { return false; } - /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */ - public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) { - mProtectedPackages.clear(); + /** + * Adds the set of package/uid pairs to set that should be blocked from screen capture + * + * @param packageInfos packages to be blocked + * @return {@code true} if packages set is modified, {@code false} otherwise. + */ + public boolean addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) { + if (mProtectedPackages.equals(packageInfos)) { + // new set is equal to current set of packages, no need to update + return false; + } mProtectedPackages.addAll(packageInfos); + return true; + } + + /** + * Clears the set of package/uid pairs that should be blocked from screen capture + * + * @return {@code true} if packages set is modified, {@code false} otherwise. + */ + public boolean clearBlockedApps() { + if (mProtectedPackages.isEmpty()) { + // set was already empty + return false; + } + mProtectedPackages.clear(); + return true; } void dump(PrintWriter pw) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 314d720692f7..8f9ed8353456 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3558,9 +3558,11 @@ class Task extends TaskFragment { && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled(); appCompatTaskInfo.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap(); - if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) { + if (top != null) { appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width(); appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height(); + } + if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) { if (appCompatTaskInfo.isTopActivityPillarboxed()) { // Pillarboxed appCompatTaskInfo.topActivityLetterboxHorizontalPosition = diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 22b690e85c35..f2a58e54bfbe 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -32,6 +32,7 @@ import android.hardware.display.VirtualDisplayConfig; import android.os.Bundle; import android.os.IBinder; import android.os.Message; +import android.util.ArraySet; import android.util.Pair; import android.view.ContentRecordingSession; import android.view.Display; @@ -1020,5 +1021,13 @@ public abstract class WindowManagerInternal { * * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture */ - public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos); + public abstract void addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos); + + /** + * Clears apps added to collection of apps in which screen capture should be disabled. + * + * <p> This clears and resets any existing set or added applications from + * * {@link #addBlockScreenCaptureForApps(ArraySet)} + */ + public abstract void clearBlockedApps(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9179acf2caed..6c833565119f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -370,7 +370,6 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -8576,10 +8575,23 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) { + public void addBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) { synchronized (mGlobalLock) { - mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos); - WindowManagerService.this.refreshScreenCaptureDisabled(); + boolean modified = + mSensitiveContentPackages.addBlockScreenCaptureForApps(packageInfos); + if (modified) { + WindowManagerService.this.refreshScreenCaptureDisabled(); + } + } + } + + @Override + public void clearBlockedApps() { + synchronized (mGlobalLock) { + boolean modified = mSensitiveContentPackages.clearBlockedApps(); + if (modified) { + WindowManagerService.this.refreshScreenCaptureDisabled(); + } } } } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 775570cb08ba..dfa9dcecfbb5 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -37,7 +37,6 @@ cc_library_static { "com_android_server_adb_AdbDebuggingManager.cpp", "com_android_server_am_BatteryStatsService.cpp", "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp", - "com_android_server_BootReceiver.cpp", "com_android_server_ConsumerIrService.cpp", "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", @@ -95,16 +94,6 @@ cc_library_static { header_libs: [ "bionic_libc_platform_headers", ], - - static_libs: [ - "libunwindstack", - ], - - whole_static_libs: [ - "libdebuggerd_tombstone_proto_to_text", - ], - - runtime_libs: ["libdexfile"], } cc_defaults { diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 15eb7c64a40c..cc08488742b2 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -33,7 +33,3 @@ per-file com_android_server_companion_virtual_InputController.cpp = file:/servic # Bug component : 158088 = per-file *AnrTimer* per-file *AnrTimer* = file:/PERFORMANCE_OWNERS - -# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java -per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS -per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp deleted file mode 100644 index 3892d284dafb..000000000000 --- a/services/core/jni/com_android_server_BootReceiver.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -#include <libdebuggerd/tombstone.h> -#include <nativehelper/JNIHelp.h> - -#include <sstream> - -#include "jni.h" -#include "tombstone.pb.h" - -namespace android { - -static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) { - ss << line << std::endl; -} - -static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject, - jbyteArray tombstoneBytes) { - Tombstone tombstone; - tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0), - env->GetArrayLength(tombstoneBytes)); - - std::stringstream tombstoneString; - - tombstone_proto_to_text(tombstone, - std::bind(&writeToString, std::ref(tombstoneString), - std::placeholders::_1, std::placeholders::_2)); - - return env->NewStringUTF(tombstoneString.str().c_str()); -} - -static const JNINativeMethod sMethods[] = { - /* name, signature, funcPtr */ - {"getTombstoneText", "([B)Ljava/lang/String;", - (jstring*)com_android_server_BootReceiver_getTombstoneText}, -}; - -int register_com_android_server_BootReceiver(JNIEnv* env) { - return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods, - NELEM(sMethods)); -} - -} // namespace android diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 414339d3f349..2b9bb7a4de87 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -685,6 +685,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon { // acquire lock std::scoped_lock _l(mLock); + outConfig->mousePointerSpeed = mLocked.pointerSpeed; + outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled; outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT); outConfig->pointerVelocityControlParameters.acceleration = diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index a4b1f841d3bc..5d1eb496903b 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -66,7 +66,6 @@ int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); int register_android_server_app_GameManagerService(JNIEnv* env); -int register_com_android_server_BootReceiver(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); @@ -129,7 +128,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); register_android_server_app_GameManagerService(env); - register_com_android_server_BootReceiver(env); register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index 94caf2865b66..c8a65459d3df 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -17,6 +17,7 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.util.Slog import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.MutateStateScope @@ -84,6 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { appOpName: String, mode: Int ): Boolean { + if (userId !in newState.userStates) { + Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId") + return false + } val defaultMode = AppOpsManager.opToDefaultMode(appOpName) val oldMode = newState.userStates[userId]!! @@ -152,4 +157,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { */ abstract fun onStateMutated() } + + companion object { + private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName + } } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 8f464d41792d..3ee7430fc486 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -17,29 +17,62 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.os.Binder import android.os.Handler import android.os.UserHandle +import android.permission.flags.Flags import android.util.ArrayMap import android.util.ArraySet +import android.util.LongSparseArray +import android.util.Slog +import android.util.SparseArray import android.util.SparseBooleanArray import android.util.SparseIntArray import com.android.internal.annotations.VisibleForTesting +import com.android.internal.util.IntPair import com.android.server.appop.AppOpsCheckingServiceInterface import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener import com.android.server.permission.access.AccessCheckingService import com.android.server.permission.access.AppOpUri +import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.PackageUri +import com.android.server.permission.access.PermissionUri import com.android.server.permission.access.UidUri +import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED +import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND +import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set +import com.android.server.permission.access.permission.AppIdPermissionPolicy +import com.android.server.permission.access.permission.PermissionFlags +import com.android.server.permission.access.permission.PermissionService class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy + private val permissionPolicy = + service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy private val context = service.context + + // Maps appop code to its runtime permission + private val runtimeAppOpToPermissionNames = SparseArray<String>() + + // Maps runtime permission to its appop codes + private val runtimePermissionNameToAppOp = ArrayMap<String, Int>() + + private var foregroundableOps = SparseBooleanArray() + + /* Maps foreground permissions to their background permission. Background permissions aren't + required to be runtime */ + private val foregroundToBackgroundPermissionName = ArrayMap<String, String>() + + /* Maps background permissions to their foreground permissions. Background permissions aren't + required to be runtime */ + private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>() + private lateinit var handler: Handler @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() @@ -68,11 +101,58 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun systemReady() { - // Not implemented because upgrades are handled automatically. + if (useRuntimePermissionAppOpMapping()) { + createPermissionAppOpMapping() + permissionPolicy.addOnPermissionFlagsChangedListener(OnPermissionFlagsChangedListener()) + } + } + + private fun createPermissionAppOpMapping() { + val permissions = service.getState { with(permissionPolicy) { getPermissions() } } + + for (appOpCode in 0 until AppOpsManager._NUM_OP) { + AppOpsManager.opToPermission(appOpCode)?.let { permissionName -> + // Multiple ops might map to a single permission but only one is considered the + // runtime appop calculations. + if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) { + val permission = permissions[permissionName]!! + if (permission.isRuntime) { + runtimePermissionNameToAppOp[permissionName] = appOpCode + runtimeAppOpToPermissionNames[appOpCode] = permissionName + permission.permissionInfo.backgroundPermission?.let { + backgroundPermissionName -> + // Note: background permission may not be runtime, + // e.g. microphone/camera. + foregroundableOps[appOpCode] = true + foregroundToBackgroundPermissionName[permissionName] = + backgroundPermissionName + backgroundToForegroundPermissionNames + .getOrPut(backgroundPermissionName, ::ArraySet) + .add(permissionName) + } + } + } + } + } } override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray { - return opNameMapToOpSparseArray(getUidModes(uid)) + val appId = UserHandle.getAppId(uid) + val userId = UserHandle.getUserId(uid) + service.getState { + val modes = + with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) } + if (useRuntimePermissionAppOpMapping()) { + runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode -> + val mode = getUidModeFromPermissionState(appId, userId, permissionName) + if (mode != AppOpsManager.opToDefaultMode(appOpCode)) { + modes[appOpCode] = mode + } + } + } + + return modes + } } override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray { @@ -83,7 +163,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + val permissionName = runtimeAppOpToPermissionNames[op] + + return if (!useRuntimePermissionAppOpMapping() || permissionName == null) { + service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } + } else { + service.getState { getUidModeFromPermissionState(appId, userId, permissionName) } + } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -92,13 +178,63 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map } - override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean { + private fun GetStateScope.getUidModeFromPermissionState( + appId: Int, + userId: Int, + permissionName: String + ): Int { + val permissionFlags = + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName] + val backgroundPermissionFlags = + if (backgroundPermissionName != null) { + with(permissionPolicy) { + getPermissionFlags(appId, userId, backgroundPermissionName) + } + } else { + PermissionFlags.RUNTIME_GRANTED + } + val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags) + if (result != MODE_IGNORED) { + return result + } + + val fullerPermissionName = + PermissionService.getFullerPermission(permissionName) ?: return result + return getUidModeFromPermissionState(appId, userId, fullerPermissionName) + } + + private fun evaluateModeFromPermissionFlags( + foregroundFlags: Int, + backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED + ): Int = + if (PermissionFlags.isAppOpGranted(foregroundFlags)) { + if (PermissionFlags.isAppOpGranted(backgroundFlags)) { + MODE_ALLOWED + } else { + MODE_FOREGROUND + } + } else { + MODE_IGNORED + } + + override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean { + if (useRuntimePermissionAppOpMapping() && code in runtimeAppOpToPermissionNames) { + Slog.w( + LOG_TAG, + "Cannot set UID mode for runtime permission app op, uid = $uid," + + " code = ${AppOpsManager.opToName(code)}, mode = ${AppOpsManager.modeToName(mode)}", + RuntimeException() + ) + return false + } + val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - val opName = AppOpsManager.opToPublicName(op) - var wasChanged = false + val appOpName = AppOpsManager.opToPublicName(code) + var wasChanged: Boolean service.mutateState { - wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) } + wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) } } return wasChanged } @@ -113,10 +249,22 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map - override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { - val opName = AppOpsManager.opToPublicName(op) + override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) { + val appOpName = AppOpsManager.opToPublicName(appOpCode) + + if ( + useRuntimePermissionAppOpMapping() && runtimeAppOpToPermissionNames.contains(appOpCode) + ) { + Slog.w( + LOG_TAG, + "(packageName=$packageName, userId=$userId)'s appop state" + + " for runtime op $appOpName should not be set directly.", + RuntimeException() + ) + return + } service.mutateState { - with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) } + with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) } } } @@ -127,7 +275,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } override fun removePackage(packageName: String, userId: Int): Boolean { - var wasChanged = false + var wasChanged: Boolean service.mutateState { wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } @@ -157,6 +305,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (useRuntimePermissionAppOpMapping()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -167,6 +322,13 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS this[AppOpsManager.strOpToOp(op)] = true } } + if (useRuntimePermissionAppOpMapping()) { + foregroundableOps.forEachIndexed { _, op, _ -> + if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) { + this[op] = true + } + } + } } } @@ -188,9 +350,10 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS } } - inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() { + private inner class OnAppIdAppOpModeChangedListener : + AppIdAppOpPolicy.OnAppOpModeChangedListener() { // (uid, appOpCode) -> newMode - val pendingChanges = ArrayMap<Pair<Int, Int>, Int>() + private val pendingChanges = LongSparseArray<Int>() override fun onAppOpModeChanged( appId: Int, @@ -201,7 +364,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS ) { val uid = UserHandle.getUid(userId, appId) val appOpCode = AppOpsManager.strOpToOp(appOpName) - val key = Pair(uid, appOpCode) + val key = IntPair.of(uid, appOpCode) pendingChanges[key] = newMode } @@ -210,8 +373,8 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS val listenersLocal = listeners pendingChanges.forEachIndexed { _, key, mode -> listenersLocal.forEachIndexed { _, listener -> - val uid = key.first - val appOpCode = key.second + val uid = IntPair.first(key) + val appOpCode = IntPair.second(key) listener.onUidModeChanged(uid, appOpCode, mode) } @@ -224,7 +387,7 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS private inner class OnPackageAppOpModeChangedListener : PackageAppOpPolicy.OnAppOpModeChangedListener() { // (packageName, userId, appOpCode) -> newMode - val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() + private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>() override fun onAppOpModeChanged( packageName: String, @@ -254,4 +417,115 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS pendingChanges.clear() } } + + private inner class OnPermissionFlagsChangedListener : + AppIdPermissionPolicy.OnPermissionFlagsChangedListener { + // (uid, appOpCode) -> newMode + private val pendingChanges = LongSparseArray<Int>() + + override fun onPermissionFlagsChanged( + appId: Int, + userId: Int, + permissionName: String, + oldFlags: Int, + newFlags: Int + ) { + backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions -> + // This is a background permission; there may be multiple foreground permissions + // affected. + foregroundPermissions.forEachIndexed { _, foregroundPermissionName -> + runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode -> + val foregroundPermissionFlags = + getPermissionFlags(appId, userId, foregroundPermissionName) + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + foregroundPermissionFlags, + oldFlags, + foregroundPermissionFlags, + newFlags + ) + } + } + } + ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission + -> + runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + val backgroundPermissionFlags = + getPermissionFlags(appId, userId, backgroundPermission) + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + oldFlags, + backgroundPermissionFlags, + newFlags, + backgroundPermissionFlags + ) + } + } + ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode -> + addPendingChangedModeIfNeeded( + appId, + userId, + appOpCode, + oldFlags, + PermissionFlags.RUNTIME_GRANTED, + newFlags, + PermissionFlags.RUNTIME_GRANTED + ) + } + } + + private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + service.getState { + with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) } + } + + private fun addPendingChangedModeIfNeeded( + appId: Int, + userId: Int, + appOpCode: Int, + oldForegroundFlags: Int, + oldBackgroundFlags: Int, + newForegroundFlags: Int, + newBackgroundFlags: Int, + ) { + val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags) + val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags) + + if (oldMode != newMode) { + val uid = UserHandle.getUid(userId, appId) + pendingChanges[IntPair.of(uid, appOpCode)] = newMode + } + } + + override fun onStateMutated() { + val listenersLocal = listeners + pendingChanges.forEachIndexed { _, key, mode -> + listenersLocal.forEachIndexed { _, listener -> + val uid = IntPair.first(key) + val appOpCode = IntPair.second(key) + + listener.onUidModeChanged(uid, appOpCode, mode) + } + } + + pendingChanges.clear() + } + } + + companion object { + private val LOG_TAG = AppOpService::class.java.simpleName + + private fun useRuntimePermissionAppOpMapping(): Boolean { + val token = Binder.clearCallingIdentity() + return try { + Flags.runtimePermissionAppopsMapping() + } finally { + Binder.restoreCallingIdentity(token) + } + } + } } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 0d9470edc4ea..2f15dc7b232a 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -17,6 +17,7 @@ package com.android.server.permission.access.appop import android.app.AppOpsManager +import android.util.Slog import com.android.server.permission.access.GetStateScope import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.MutateStateScope @@ -87,6 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { appOpName: String, mode: Int ): Boolean { + if (userId !in newState.userStates) { + Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId") + return false + } val defaultMode = AppOpsManager.opToDefaultMode(appOpName) val oldMode = newState.userStates[userId]!! @@ -155,4 +160,8 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { */ abstract fun onStateMutated() } + + companion object { + private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName + } } diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt new file mode 100644 index 000000000000..827dd0e5d292 --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 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.permission.access.collection + +import android.util.LongSparseArray + +inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val <T> LongSparseArray<T>.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) { + delete(key) +} + +inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline val <T> LongSparseArray<T>.size: Int + get() = size() + +@Suppress("NOTHING_TO_INLINE") +inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) { + put(key, value) +} diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt new file mode 100644 index 000000000000..a582431aa83c --- /dev/null +++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 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.permission.access.collection + +import android.util.SparseIntArray + +inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (!predicate(index, key, value)) { + return false + } + } + return true +} + +inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return true + } + } + return false +} + +inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) { + for (index in 0 until size) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) { + for (index in lastIndex downTo 0) { + action(index, keyAt(index), valueAt(index)) + } +} + +inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + valueAt(index) + } else { + defaultValue().also { put(key, it) } + } +} + +inline val SparseIntArray.lastIndex: Int + get() = size - 1 + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.minusAssign(key: Int) { + delete(key) +} + +inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + forEachIndexed { index, key, value -> + if (predicate(index, key, value)) { + return false + } + } + return true +} + +fun SparseIntArray.remove(key: Int) { + delete(key) +} + +fun SparseIntArray.remove(key: Int, defaultValue: Int): Int { + val index = indexOfKey(key) + return if (index >= 0) { + val oldValue = valueAt(index) + removeAt(index) + oldValue + } else { + defaultValue + } +} + +inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean { + var isChanged = false + forEachReversedIndexed { index, key, value -> + if (!predicate(index, key, value)) { + removeAt(index) + isChanged = true + } + } + return isChanged +} + +@Suppress("NOTHING_TO_INLINE") +inline operator fun SparseIntArray.set(key: Int, value: Int) { + put(key, value) +} + +inline val SparseIntArray.size: Int + get() = size() diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 022268df4a63..62d2d7ee848a 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -134,7 +134,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { val changedPermissionNames = MutableIndexedSet<String>() packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + // The package may still be removed even if it was once notified as installed. + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed adoptPermissions(packageState, changedPermissionNames) addPermissionGroups(packageState) addPermissions(packageState, changedPermissionNames) @@ -147,12 +149,14 @@ class AppIdPermissionPolicy : SchemePolicy() { } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed val installedPackageState = if (isSystemUpdated) packageState else null evaluateAllPermissionStatesForPackage(packageState, installedPackageState) } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName]!! + val packageState = newState.externalState.packageStates[packageName] + ?: return@forEachIndexed newState.externalState.userIds.forEachIndexed { _, userId -> inheritImplicitPermissionStates(packageState.appId, userId) } @@ -1607,6 +1611,13 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { + if (userId !in newState.userStates) { + // Despite that we check UserManagerInternal.exists() in PermissionService, we may still + // sometimes get race conditions between that check and the actual mutateState() call. + // This should rarely happen but at least we should not crash. + Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId") + return false + } val oldFlags = newState.userStates[userId]!! .appIdPermissionFlags[appId] diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index f469ab547763..b162a1b88b76 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2870,5 +2870,8 @@ class PermissionService(private val service: AccessCheckingService) : } else { emptySet<String>() } + + fun getFullerPermission(permissionName: String): String? = + FULLER_PERMISSIONS[permissionName] } } diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java index b363fd4cc7cb..d78143381ae5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java @@ -20,11 +20,13 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.media.projection.MediaProjectionInfo; @@ -35,6 +37,7 @@ import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper.RunWithLooper; +import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -52,7 +55,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.Collections; import java.util.Set; @SmallTest @@ -68,6 +70,8 @@ public class SensitiveContentProtectionManagerServiceTest { private static final int NOTIFICATION_UID_1 = 5; private static final int NOTIFICATION_UID_2 = 6; + private static final ArraySet<PackageInfo> EMPTY_SET = new ArraySet<>(); + @Rule public final TestableContext mContext = new TestableContext(getInstrumentation().getTargetContext(), null); @@ -107,6 +111,9 @@ public class SensitiveContentProtectionManagerServiceTest { mSensitiveContentProtectionManagerService.mNotificationListener = spy(mSensitiveContentProtectionManagerService.mNotificationListener); + doCallRealMethod() + .when(mSensitiveContentProtectionManagerService.mNotificationListener) + .onListenerConnected(); // Setup RankingMap and two possilbe rankings when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true); @@ -128,7 +135,7 @@ public class SensitiveContentProtectionManagerServiceTest { mSensitiveContentProtectionManagerService.onDestroy(); } - private Set<PackageInfo> setupSensitiveNotification() { + private ArraySet<PackageInfo> setupSensitiveNotification() { // Setup Notification Values when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1); when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1); @@ -149,10 +156,11 @@ public class SensitiveContentProtectionManagerServiceTest { when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2))) .thenReturn(mNonSensitiveRanking); - return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)); + return new ArraySet<>( + Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1))); } - private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() { + private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() { // Setup Notification Values when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1); when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1); @@ -173,10 +181,11 @@ public class SensitiveContentProtectionManagerServiceTest { when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2))) .thenReturn(mSensitiveRanking); - return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)); + return new ArraySet<>( + Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1))); } - private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() { + private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() { // Setup Notification Values when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1); when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1); @@ -197,11 +206,12 @@ public class SensitiveContentProtectionManagerServiceTest { when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2))) .thenReturn(mSensitiveRanking); - return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1), - new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1)); + return new ArraySet<>( + Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1), + new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1))); } - private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() { + private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() { // Setup Notification Values when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1); when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1); @@ -222,8 +232,9 @@ public class SensitiveContentProtectionManagerServiceTest { when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2))) .thenReturn(mSensitiveRanking); - return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1), - new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2)); + return new ArraySet<>( + Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1), + new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2))); } private void setupNoSensitiveNotifications() { @@ -251,11 +262,11 @@ public class SensitiveContentProtectionManagerServiceTest { @Test public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() { - Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); + ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @Test @@ -264,7 +275,7 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); } @Test @@ -273,37 +284,37 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); } @Test public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() { - Set<PackageInfo> expectedBlockedPackages = + ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromSamePackageAndUid(); mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @Test public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() { - Set<PackageInfo> expectedBlockedPackages = + ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromDifferentPackage(); mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @Test public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() { - Set<PackageInfo> expectedBlockedPackages = + ArraySet<PackageInfo> expectedBlockedPackages = setupMultipleSensitiveNotificationsFromDifferentUid(); mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @Test @@ -316,12 +327,12 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).clearBlockedApps(); } @Test public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() { - Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); + ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class); mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); @@ -330,7 +341,7 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); } @Test @@ -341,7 +352,7 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); } @Test @@ -352,7 +363,7 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); } @Test @@ -363,7 +374,7 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); } @Test @@ -376,6 +387,314 @@ public class SensitiveContentProtectionManagerServiceTest { mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); - verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet()); + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnListenerConnected_projectionNotStarted_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnListenerConnected_projectionStopped_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnListenerConnected_projectionStarted_setWmBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); + } + + @Test + public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() { + setupNoSensitiveNotifications(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnListenerConnected_noNotifications_noBlockedPackages() { + setupNoNotifications(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + doReturn(null) + .when(mSensitiveContentProtectionManagerService.mNotificationListener) + .getCurrentRanking(); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnListenerConnected_missingRanking_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); + doReturn(mRankingMap) + .when(mSensitiveContentProtectionManagerService.mNotificationListener) + .getCurrentRanking(); + + mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationRankingUpdate_projectionStopped_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationRankingUpdate_projectionStarted_setWmBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); + } + + @Test + public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() { + setupNoSensitiveNotifications(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() { + setupNoNotifications(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationRankingUpdate_nullRankingMap_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(null); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationRankingUpdate_missingRanking_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); + doReturn(mRankingMap) + .when(mSensitiveContentProtectionManagerService.mNotificationListener) + .getCurrentRanking(); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationRankingUpdate_getActiveNotificationsThrows_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + doThrow(SecurityException.class) + .when(mSensitiveContentProtectionManagerService.mNotificationListener) + .getActiveNotifications(); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationRankingUpdate(mRankingMap); + + verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET); + } + + @Test + public void nlsOnNotificationPosted_projectionNotStarted_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification1, mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationPosted_projectionStopped_noop() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification1, mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationPosted_projectionStarted_setWmBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification1, mRankingMap); + + ArraySet<PackageInfo> expectedBlockedPackages = new ArraySet<>( + Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1))); + verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages); + } + + @Test + public void nlsOnNotificationPosted_noSensitiveNotifications_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification2, mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationPosted_noNotifications_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(null, mRankingMap); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationPosted_nullRankingMap_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification1, null); + + verifyZeroInteractions(mWindowManager); + } + + @Test + public void nlsOnNotificationPosted_missingRanking_noBlockedPackages() { + // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2 + // as non-sensitive + setupSensitiveNotification(); + mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class)); + Mockito.reset(mWindowManager); + when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null); + + mSensitiveContentProtectionManagerService.mNotificationListener + .onNotificationPosted(mNotification1, mRankingMap); + + verifyZeroInteractions(mWindowManager); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java new file mode 100644 index 000000000000..7d3a1103a044 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static com.android.server.am.ActivityManagerService.Injector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.app.ApplicationStartInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.os.FileUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.platform.test.annotations.Presubmit; +import android.text.TextUtils; + +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; + +/** + * Test class for {@link android.app.ApplicationStartInfo}. + * + * Build/Install/Run: + * atest ApplicationStartInfoTest + */ +@Presubmit +public class ApplicationStartInfoTest { + + private static final String TAG = ApplicationStartInfoTest.class.getSimpleName(); + private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo"); + + @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule(); + @Mock private AppOpsService mAppOpsService; + @Mock private PackageManagerInternal mPackageManagerInt; + + private Context mContext = getInstrumentation().getTargetContext(); + private TestInjector mInjector; + private ActivityManagerService mAms; + private ProcessList mProcessList; + private AppStartInfoTracker mAppStartInfoTracker; + private Handler mHandler; + private HandlerThread mHandlerThread; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mProcessList = spy(new ProcessList()); + mAppStartInfoTracker = spy(new AppStartInfoTracker()); + mAppStartInfoTracker.mEnabled = true; + setFieldValue(ProcessList.class, mProcessList, "mAppStartInfoTracker", + mAppStartInfoTracker); + mInjector = new TestInjector(mContext); + mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread()); + mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); + mAms.mPackageManagerInt = mPackageManagerInt; + mAppStartInfoTracker.mService = mAms; + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doReturn("com.android.test").when(mPackageManagerInt).getNameForUid(anyInt()); + // Remove stale instance of PackageManagerInternal if there is any + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + } + + @After + public void tearDown() { + mHandlerThread.quit(); + } + + @Test + public void testApplicationStartInfo() throws Exception { + mAppStartInfoTracker.clearProcessStartInfo(true); + mAppStartInfoTracker.mAppStartInfoLoaded.set(true); + mAppStartInfoTracker.mAppStartInfoHistoryListSize = + mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE; + mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(), + AppStartInfoTracker.APP_START_STORE_DIR); + assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir)); + mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir, + AppStartInfoTracker.APP_START_INFO_FILE); + + doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean()); + + final int app1Uid = 10123; + final int app1Pid1 = 12345; + final int app1Pid2 = 12346; + final int app1DefiningUid = 23456; + final int app1UidUser2 = 1010123; + final int app1PidUser2 = 12347; + final String app1ProcessName = "com.android.test.stub1:process"; + final String app1PackageName = "com.android.test.stub1"; + final long appStartTimestampIntentStarted = 1000000; + final long appStartTimestampActivityLaunchFinished = 2000000; + final long appStartTimestampReportFullyDrawn = 3000000; + final long appStartTimestampService = 4000000; + final long appStartTimestampBroadcast = 5000000; + final long appStartTimestampRContentProvider = 6000000; + + ProcessRecord app = makeProcessRecord( + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + app1PackageName); // packageName + + ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>(); + + // Case 1: Activity start intent failed + mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + appStartTimestampIntentStarted); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 0); + + verifyInProgApplicationStartInfo( + 0, // index + 0, // pid + 0, // uid + 0, // packageUid + null, // definingUid + null, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_UNSET, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(0); + assertEquals(list.size(), 0); + + mAppStartInfoTracker.clearProcessStartInfo(true); + + // Case 2: Activity start launch cancelled + mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + appStartTimestampIntentStarted); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 0); + + mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT, + ApplicationStartInfo.START_TYPE_COLD, app); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 1); + + verifyInProgApplicationStartInfo( + 0, // index + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(0); + assertEquals(list.size(), 1); + + verifyApplicationStartInfo( + list.get(0), // info + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_ERROR, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + mAppStartInfoTracker.clearProcessStartInfo(true); + + // Case 3: Activity start success + mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + appStartTimestampIntentStarted); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 0); + + mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT, + ApplicationStartInfo.START_TYPE_COLD, app); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 1); + + verifyInProgApplicationStartInfo( + 0, // index + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + verifyApplicationStartInfo( + list.get(0), // info + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT, + appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(1); + assertEquals(list.size(), 1); + + verifyInProgApplicationStartInfo( + 0, // index + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted, + appStartTimestampReportFullyDrawn); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); + verifyInProgressRecordsSize(0); + assertEquals(list.size(), 1); + + verifyApplicationStartInfo( + list.get(0), // info + app1Pid1, // pid + app1Uid, // uid + app1Uid, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason + ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + // Don't clear records for use in subsequent cases. + + // Case 4: Create an other app1 record with different pid started for a service + sleep(1); + app = makeProcessRecord( + app1Pid2, // pid + app1Uid, // uid + app1Uid, // packageUid + app1DefiningUid, // definingUid + app1ProcessName, // processName + app1PackageName); // packageName + ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms); + + mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service, + false); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list); + assertEquals(list.size(), 2); + + verifyApplicationStartInfo( + list.get(0), // info + app1Pid2, // pid + app1Uid, // uid + app1Uid, // packageUid + app1DefiningUid, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_SERVICE, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_WARM, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + // Case 5: Create an instance of app1 with a different user started for a broadcast + sleep(1); + app = makeProcessRecord( + app1PidUser2, // pid + app1UidUser2, // uid + app1UidUser2, // packageUid + null, // definingUid + app1ProcessName, // processName + app1PackageName); // packageName + + mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app, + null, true /* isColdStart */); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list); + assertEquals(list.size(), 1); + + verifyApplicationStartInfo( + list.get(0), // info + app1PidUser2, // pid + app1UidUser2, // uid + app1UidUser2, // packageUid + null, // definingUid + app1ProcessName, // processName + ApplicationStartInfo.START_REASON_BROADCAST, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_COLD, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + // Case 6: User 2 gets removed + mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false); + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list); + assertEquals(list.size(), 0); + + list.clear(); + mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list); + assertEquals(list.size(), 2); + + + // Case 7: Create a process from another package started for a content provider + final int app2UidUser2 = 1010234; + final int app2PidUser2 = 12348; + final String app2ProcessName = "com.android.test.stub2:process"; + final String app2PackageName = "com.android.test.stub2"; + + sleep(1); + + app = makeProcessRecord( + app2PidUser2, // pid + app2UidUser2, // uid + app2UidUser2, // packageUid + null, // definingUid + app2ProcessName, // processName + app2PackageName); // packageName + + mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider, + app, false); + list.clear(); + mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list); + assertEquals(list.size(), 1); + + verifyApplicationStartInfo( + list.get(0), // info + app2PidUser2, // pid + app2UidUser2, // uid + app2UidUser2, // packageUid + null, // definingUid + app2ProcessName, // processName + ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason + ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state + ApplicationStartInfo.START_TYPE_WARM, // state type + ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + + // Case 8: Save and load again + ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>(); + mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original); + assertTrue(original.size() > 0); + + mAppStartInfoTracker.persistProcessStartInfo(); + assertTrue(mAppStartInfoTracker.mProcStartInfoFile.exists()); + + mAppStartInfoTracker.clearProcessStartInfo(false); + list.clear(); + mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list); + assertEquals(0, list.size()); + + mAppStartInfoTracker.loadExistingProcessStartInfo(); + list.clear(); + mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list); + assertEquals(original.size(), list.size()); + + for (int i = list.size() - 1; i >= 0; i--) { + assertTrue(list.get(i).equals(original.get(i))); + } + } + + private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + Field mfield = Field.class.getDeclaredField("accessFlags"); + mfield.setAccessible(true); + mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE)); + field.set(obj, val); + } catch (NoSuchFieldException | IllegalAccessException e) { + } + } + + private void sleep(long ms) { + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + } + } + + private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, + String processName, String packageName) { + return makeProcessRecord(pid, uid, packageUid, definingUid, processName, packageName, mAms); + } + + @SuppressWarnings("GuardedBy") + static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, + String processName, String packageName, ActivityManagerService ams) { + ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ProcessRecord app = new ProcessRecord(ams, ai, processName, uid); + app.setPid(pid); + app.info.uid = packageUid; + if (definingUid != null) { + app.setHostingRecord(HostingRecord.byAppZygote(COMPONENT, "", definingUid, "")); + } + return app; + } + + private static Intent buildIntent(ComponentName componentName) throws Exception { + Intent intent = new Intent(); + intent.setComponent(componentName); + intent.setPackage(componentName.getPackageName()); + return intent; + } + + private void verifyInProgressRecordsSize(int expectedSize) { + synchronized (mAppStartInfoTracker.mLock) { + assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize); + } + } + + private void verifyInProgApplicationStartInfo(int index, + Integer pid, Integer uid, Integer packageUid, + Integer definingUid, String processName, + Integer reason, Integer startupState, Integer startType, Integer launchMode) { + synchronized (mAppStartInfoTracker.mLock) { + verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index), + pid, uid, packageUid, definingUid, processName, reason, startupState, + startType, launchMode); + } + } + + private void verifyApplicationStartInfo(ApplicationStartInfo info, + Integer pid, Integer uid, Integer packageUid, + Integer definingUid, String processName, + Integer reason, Integer startupState, Integer startType, Integer launchMode) { + assertNotNull(info); + + if (pid != null) { + assertEquals(pid.intValue(), info.getPid()); + } + if (uid != null) { + assertEquals(uid.intValue(), info.getRealUid()); + } + if (packageUid != null) { + assertEquals(packageUid.intValue(), info.getPackageUid()); + } + if (definingUid != null) { + assertEquals(definingUid.intValue(), info.getDefiningUid()); + } + if (processName != null) { + assertTrue(TextUtils.equals(processName, info.getProcessName())); + } + if (reason != null) { + assertEquals(reason.intValue(), info.getReason()); + } + if (startupState != null) { + assertEquals(startupState.intValue(), info.getStartupState()); + } + if (startType != null) { + assertEquals(startType.intValue(), info.getStartType()); + } + if (launchMode != null) { + assertEquals(launchMode.intValue(), info.getLaunchMode()); + } + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile, + Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandler; + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mProcessList; + } + } + + static class ServiceThreadRule implements TestRule { + + private ServiceThread mThread; + + ServiceThread getThread() { + return mThread; + } + + @Override + public Statement apply(Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + mThread = new ServiceThread("TestServiceThread", + Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); + mThread.start(); + try { + base.evaluate(); + } finally { + mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */); + } + } + }; + } + } + + // TODO: [b/302724778] Remove manual JNI load + static { + System.loadLibrary("mockingservicestestjni"); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 116d5db45023..97767a5dbd89 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -426,6 +426,8 @@ public class FlexibilityControllerTest { @Test public void testGetNextConstraintDropTimeElapsedLocked() { + setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS); + long nextTimeToDropNumConstraints; // no delay, deadline @@ -457,15 +459,18 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(130400100, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2, + nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(156320100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10, + nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(182240100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10, + nextTimeToDropNumConstraints); // no delay, no deadline jb = createJob(0); @@ -473,15 +478,15 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(129600100, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(155520100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints); js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); - assertEquals(181440100L, nextTimeToDropNumConstraints); + assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints); // delay, deadline jb = createJob(0) @@ -1328,7 +1333,7 @@ public class FlexibilityControllerTest { final ArraySet<String> pkgs = new ArraySet<>(); pkgs.add(js.getSourcePackageName()); - when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs); + doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid); setUidBias(mSourceUid, BIAS_TOP_APP); setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index 0403c64fc624..ec7e35982311 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -233,7 +233,7 @@ public class PackageArchiverTest { Exception e = assertThrows( SecurityException.class, () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e).hasMessageThat().isEqualTo( String.format( "The UID %s of callerPackageName set by the caller doesn't match the " @@ -250,7 +250,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( String.format("Package %s not found.", PACKAGE)); @@ -260,8 +260,7 @@ public class PackageArchiverTest { public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException { mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false); - mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT, - 0); + mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -291,7 +290,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found"); } @@ -305,7 +304,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( "Installer does not support unarchival"); @@ -319,7 +318,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE)); @@ -331,8 +330,7 @@ public class PackageArchiverTest { doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE), any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt()); - mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT, - 0); + mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -355,7 +353,7 @@ public class PackageArchiverTest { Exception e = assertThrows( ParcelableException.class, () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, - UserHandle.CURRENT, 0)); + UserHandle.CURRENT)); assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class); assertThat(e.getCause()).hasMessageThat().isEqualTo( TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE)); @@ -363,8 +361,7 @@ public class PackageArchiverTest { @Test public void archiveApp_withNoAdditionalFlags_success() { - mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT, - 0); + mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); verify(mInstallerService).uninstall( @@ -386,14 +383,13 @@ public class PackageArchiverTest { @Test public void archiveApp_withAdditionalFlags_success() { - mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT, - PackageManager.DELETE_SHOW_DIALOG); + mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT); rule.mocks().getHandler().flush(); verify(mInstallerService).uninstall( eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)), eq(CALLER_PACKAGE), - eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG), + eq(DELETE_ARCHIVE | DELETE_KEEP_DATA), eq(mIntentSender), eq(UserHandle.CURRENT.getIdentifier()), anyInt()); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index fd6aa0c1ffab..e6298eeccafb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -19,9 +19,11 @@ import static android.os.UserManager.DISALLOW_OUTGOING_CALLS; import static android.os.UserManager.DISALLOW_SMS; import static android.os.UserManager.DISALLOW_USER_SWITCH; import static android.os.UserManager.USER_TYPE_FULL_SECONDARY; +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -30,20 +32,27 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; +import android.app.KeyguardManager; import android.content.Context; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; +import android.multiuser.Flags; +import android.os.PowerManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.Log; import android.util.Pair; @@ -52,6 +61,7 @@ import android.util.Xml; import androidx.test.annotation.UiThreadTest; +import com.android.dx.mockito.inline.extended.MockedVoidMethod; import com.android.internal.widget.LockSettingsInternal; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.testing.ExtendedMockitoRule; @@ -65,6 +75,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -115,8 +126,11 @@ public final class UserManagerServiceTest { .spyStatic(LocalServices.class) .spyStatic(SystemProperties.class) .mockStatic(Settings.Global.class) + .mockStatic(Settings.Secure.class) .build(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final Object mPackagesLock = new Object(); private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation() .getTargetContext(); @@ -133,6 +147,8 @@ public final class UserManagerServiceTest { private @Mock StorageManager mStorageManager; private @Mock LockSettingsInternal mLockSettingsInternal; private @Mock PackageManagerInternal mPackageManagerInternal; + private @Mock KeyguardManager mKeyguardManager; + private @Mock PowerManager mPowerManager; /** * Reference to the {@link UserManagerService} being tested. @@ -156,6 +172,8 @@ public final class UserManagerServiceTest { when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false); mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal); when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager); + when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager); + when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager); mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal); mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal); doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); @@ -550,6 +568,143 @@ public final class UserManagerServiceTest { assertTrue(hasRestrictionsInUserXMLFile(user.id)); } + @Test + public void testAutoLockOnDeviceLockForPrivateProfile() { + mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + UserManagerService mSpiedUms = spy(mUms); + UserInfo privateProfileUser = + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); + Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(), + any()); + + mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true); + + Mockito.verify(mSpiedUms).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), + any(), any()); + } + + @Test + public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() { + mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + UserManagerService mSpiedUms = spy(mUms); + UserInfo privateProfileUser = + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); + + mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false); + + // Verify that no operation to disable quiet mode is not called + Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), + any(), any()); + } + + @Test + public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + UserManagerService mSpiedUms = spy(mUms); + UserInfo privateProfileUser = + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + + mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true); + + // Verify that no auto-lock operations take place + verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(), + eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never()); + Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync( + eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), + any(), any()); + } + + @Test + public void testAutoLockAfterInactityForPrivateProfile() { + mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + UserManagerService mSpiedUms = spy(mUms); + mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); + when(mPowerManager.isInteractive()).thenReturn(false); + + UserInfo privateProfileUser = + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( + eq(privateProfileUser.getUserHandle().getIdentifier()), any(), + anyLong()); + + + mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace(); + + Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( + eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong()); + } + + @Test + public void testSetOrUpdateAutoLockPreference_noPrivateProfile() { + mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + + mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); + + Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any()); + Mockito.verify(mSpiedContext, never()).unregisterReceiver(any()); + Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any())); + Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any()); + } + + @Test + public void testSetOrUpdateAutoLockPreference() { + mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); + mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + USER_TYPE_PROFILE_PRIVATE, 0, 0, null); + + // Set the preference to auto lock on device lock + mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); + + // Verify that keyguard state listener was added + Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any()); + //Verity that keyguard state listener was not removed + Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any()); + // Broadcasts are already unregistered when UserManagerService starts and the flag + // isDeviceInactivityBroadcastReceiverRegistered is false + Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any()); + Mockito.verify(mSpiedContext, never()).unregisterReceiver(any()); + + Mockito.clearInvocations(mKeyguardManager); + Mockito.clearInvocations(mSpiedContext); + + // Now set the preference to auto-lock on inactivity + mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); + + // Verify that inactivity broadcasts are registered + Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any()); + // Verify that keyguard state listener is removed + Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any()); + // Verify that all other operations don't take place + Mockito.verify(mSpiedContext, never()).unregisterReceiver(any()); + Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any()); + + Mockito.clearInvocations(mKeyguardManager); + Mockito.clearInvocations(mSpiedContext); + + // Finally, set the preference to don't auto-lock + mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( + Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER); + + // Verify that inactivity broadcasts are unregistered and keyguard listener was removed + Mockito.verify(mSpiedContext).unregisterReceiver(any()); + Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any()); + // Verify that no broadcasts were registered and no listeners were added + Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any()); + Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any()); + } + /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. @@ -632,6 +787,12 @@ public final class UserManagerServiceTest { SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean())); } + private void mockAutoLockForPrivateSpace(int val) { + doReturn(val).when(() -> + Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), + anyInt(), anyInt())); + } + private void mockCurrentUser(@UserIdInt int userId) { mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 00450267ee79..0831086b28ca 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -29,8 +29,6 @@ android_test { "src/**/*.java", "src/**/*.kt", - "test-apps/JobTestApp/src/**/*.java", - "test-apps/SuspendTestApp/src/**/*.java", ], static_libs: [ @@ -124,7 +122,6 @@ android_test { }, data: [ - ":JobTestApp", ":SimpleServiceTestApp1", ":SimpleServiceTestApp2", ":SimpleServiceTestApp3", diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b1d50399416a..27c522d68119 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -29,7 +29,6 @@ <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> <option name="test-file-name" value="FrameworksServicesTests.apk" /> - <option name="test-file-name" value="JobTestApp.apk" /> <option name="test-file-name" value="SuspendTestApp.apk" /> <option name="test-file-name" value="SimpleServiceTestApp1.apk" /> <option name="test-file-name" value="SimpleServiceTestApp2.apk" /> diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java deleted file mode 100644 index 523c5c060cf5..000000000000 --- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2023 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; - -import static com.google.common.truth.Truth.assertThat; - -import android.test.AndroidTestCase; - -import com.android.server.os.TombstoneProtos; -import com.android.server.os.TombstoneProtos.Tombstone; - -public class BootReceiverTest extends AndroidTestCase { - private static final String TAG = "BootReceiverTest"; - - public void testRemoveMemoryFromTombstone() { - Tombstone tombstoneBase = Tombstone.newBuilder() - .setBuildFingerprint("build_fingerprint") - .setRevision("revision") - .setPid(123) - .setTid(23) - .setUid(34) - .setSelinuxLabel("selinux_label") - .addCommandLine("cmd1") - .addCommandLine("cmd2") - .addCommandLine("cmd3") - .setProcessUptime(300) - .setAbortMessage("abort") - .addCauses(TombstoneProtos.Cause.newBuilder() - .setHumanReadable("cause1") - .setMemoryError(TombstoneProtos.MemoryError.newBuilder() - .setTool(TombstoneProtos.MemoryError.Tool.SCUDO) - .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE))) - .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs( - TombstoneProtos.LogMessage.newBuilder() - .setTimestamp("123") - .setMessage("message"))) - .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path")) - .build(); - - Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder() - .putThreads(1, TombstoneProtos.Thread.newBuilder() - .setId(1) - .setName("thread1") - .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1)) - .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2)) - .addBacktraceNote("backtracenote1") - .addUnreadableElfFiles("files1") - .setTaggedAddrCtrl(1) - .setPacEnabledKeys(10) - .build()) - .build(); - - Tombstone tombstoneWithMemory = tombstoneBase.toBuilder() - .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder() - .setBeginAddress(1) - .setEndAddress(100) - .setOffset(10) - .setRead(true) - .setWrite(true) - .setExecute(false) - .setMappingName("mapping") - .setBuildId("build") - .setLoadBias(70)) - .putThreads(1, TombstoneProtos.Thread.newBuilder() - .setId(1) - .setName("thread1") - .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1)) - .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2)) - .addBacktraceNote("backtracenote1") - .addUnreadableElfFiles("files1") - .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder() - .setRegisterName("register1") - .setMappingName("mapping") - .setBeginAddress(10)) - .setTaggedAddrCtrl(1) - .setPacEnabledKeys(10) - .build()) - .build(); - - assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory)) - .isEqualTo(tombstoneWithoutMemory); - } -} diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java deleted file mode 100644 index e871fc567107..000000000000 --- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2017 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.job; - -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED; -import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.app.IActivityManager; -import android.app.job.JobParameters; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.IDeviceIdleController; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.os.UserHandle; -import android.util.Log; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.FlakyTest; -import androidx.test.filters.LargeTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.servicestests.apps.jobtestapp.TestJobActivity; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests that background restrictions on jobs work as expected. - * This test requires test-apps/JobTestApp to be installed on the device. - * To run this test from root of checkout: - * <pre> - * mmm -j32 frameworks/base/services/tests/servicestests/ - * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk - * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk - * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \ - * com.android.frameworks.servicestests - * </pre> - */ -@RunWith(AndroidJUnit4.class) -@LargeTest -public class BackgroundRestrictionsTest { - private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName(); - private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp"; - private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity"; - private static final long POLL_INTERVAL = 500; - private static final long DEFAULT_WAIT_TIMEOUT = 10_000; - - private Context mContext; - private AppOpsManager mAppOpsManager; - private IDeviceIdleController mDeviceIdleController; - private IActivityManager mIActivityManager; - private volatile int mTestJobId = -1; - private int mTestPackageUid; - /* accesses must be synchronized on itself */ - private final TestJobStatus mTestJobStatus = new TestJobStatus(); - private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY); - Log.d(TAG, "Received action " + intent.getAction()); - synchronized (mTestJobStatus) { - switch (intent.getAction()) { - case ACTION_JOB_STARTED: - mTestJobStatus.running = true; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED; - break; - case ACTION_JOB_STOPPED: - mTestJobStatus.running = false; - mTestJobStatus.jobId = params.getJobId(); - mTestJobStatus.stopReason = params.getStopReason(); - break; - } - } - } - }; - - @Before - public void setUp() throws Exception { - mContext = InstrumentationRegistry.getTargetContext(); - mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - mIActivityManager = ActivityManager.getService(); - mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0); - mTestJobStatus.reset(); - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_JOB_STARTED); - intentFilter.addAction(ACTION_JOB_STOPPED); - mContext.registerReceiver(mJobStateChangeReceiver, intentFilter, - Context.RECEIVER_EXPORTED_UNAUDITED); - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void scheduleTestJob() { - mTestJobId = (int) (SystemClock.uptimeMillis() / 1000); - final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB); - scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId); - scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - mContext.startActivity(scheduleJobIntent); - } - - private void scheduleAndAssertJobStarted() throws Exception { - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @FlakyTest - @Test - public void testPowerExemption() throws Exception { - scheduleAndAssertJobStarted(); - setAppOpsModeAllowed(false); - mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT); - assertTrue("Job did not stop after putting app under bg-restriction", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - setPowerExemption(true); - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - - setPowerExemption(false); - assertTrue("Job did not stop after removing from the power exemption list", - awaitJobStop(DEFAULT_WAIT_TIMEOUT, - JobParameters.STOP_REASON_BACKGROUND_RESTRICTION)); - - scheduleTestJob(); - Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY); - assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - setPowerExemption(true); - assertTrue("Job did not start when the app was in the power exemption list", - awaitJobStart(DEFAULT_WAIT_TIMEOUT)); - } - - @After - public void tearDown() throws Exception { - final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS); - cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY)); - cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(cancelJobsIntent); - mContext.unregisterReceiver(mJobStateChangeReceiver); - Thread.sleep(500); // To avoid race with register in the next setUp - setAppOpsModeAllowed(true); - setPowerExemption(false); - } - - private void setPowerExemption(boolean exempt) throws RemoteException { - if (exempt) { - mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE); - } else { - mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE); - } - } - - private void setAppOpsModeAllowed(boolean allow) { - mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid, - TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); - } - - private boolean awaitJobStart(long timeout) throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running; - } - }); - } - - private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason) - throws InterruptedException { - return waitUntilTrue(timeout, () -> { - synchronized (mTestJobStatus) { - return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running - && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED - || mTestJobStatus.stopReason == expectedStopReason); - } - }); - } - - private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException { - final long deadLine = SystemClock.uptimeMillis() + timeout; - do { - Thread.sleep(POLL_INTERVAL); - } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine); - return condition.isTrue(); - } - - private static final class TestJobStatus { - int jobId; - int stopReason; - boolean running; - - private void reset() { - running = false; - stopReason = jobId = 0; - } - } - - private interface Condition { - boolean isTrue(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 0f5fb91a140f..d50affba1ea1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -406,8 +406,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { public void testPushDynamicShortcut() { // Change the max number of shortcuts. - mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5"); - + mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); setCaller(CALLING_PACKAGE_1, USER_0); final ShortcutInfo s1 = makeShortcut("s1"); @@ -545,6 +545,57 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0)); } + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + throws InterruptedException { + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); + + // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled. + setCaller(CALLING_PACKAGE_1, USER_0); + { + final ShortcutInfo si = makeShortcut("s0"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well. + setCaller(CALLING_PACKAGE_2, USER_0); + { + final ShortcutInfo si = makeShortcut("s1"); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0)); + Mockito.reset(mMockUsageStatsManagerInternal); + for (int i = 2; i <= 10; i++) { + final ShortcutInfo si = makeShortcut("s" + i); + mManager.pushDynamicShortcut(si); + } + verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( + any(), any(), anyInt()); + + Mockito.reset(mMockUsageStatsManagerInternal); + // Let time passes which resets the throttle + Thread.sleep(505); + // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + setCaller(CALLING_PACKAGE_2, USER_0); + mManager.pushDynamicShortcut(makeShortcut("s10")); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_1), any(), eq(USER_0)); + verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage( + eq(CALLING_PACKAGE_2), any(), eq(USER_0)); + } + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_0); diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp deleted file mode 100644 index 6458bcd4fc8f..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2017 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_test_helper_app { - name: "JobTestApp", - - sdk_version: "current", - - srcs: ["**/*.java"], - - dex_preopt: { - enabled: false, - }, - optimize: { - enabled: false, - }, -} diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml deleted file mode 100644 index ac35805e8af6..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2017 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.servicestests.apps.jobtestapp"> - - <application> - <service android:name=".TestJobService" - android:permission="android.permission.BIND_JOB_SERVICE" /> - <activity android:name=".TestJobActivity" - android:exported="true" /> - </application> - -</manifest>
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS deleted file mode 100644 index 6f207fb1a00e..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /apex/jobscheduler/OWNERS diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java deleted file mode 100644 index 99eb196e3298..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.apps.jobtestapp; - -import android.app.Activity; -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -public class TestJobActivity extends Activity { - private static final String TAG = TestJobActivity.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - - public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID"; - public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB"; - public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS"; - public static final int JOB_INITIAL_BACKOFF = 10_000; - public static final int JOB_MINIMUM_LATENCY = 5_000; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class); - JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); - final Intent intent = getIntent(); - switch (intent.getAction()) { - case ACTION_CANCEL_JOBS: - jobScheduler.cancelAll(); - Log.d(TAG, "Cancelled all jobs for " + getPackageName()); - break; - case ACTION_START_JOB: - final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode()); - JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent) - .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR) - .setMinimumLatency(JOB_MINIMUM_LATENCY) - .setOverrideDeadline(JOB_MINIMUM_LATENCY); - final int result = jobScheduler.schedule(jobBuilder.build()); - if (result != JobScheduler.RESULT_SUCCESS) { - Log.e(TAG, "Could not schedule job " + jobId); - } else { - Log.d(TAG, "Successfully scheduled job with id " + jobId); - } - break; - default: - Log.e(TAG, "Unknown action " + intent.getAction()); - } - finish(); - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java deleted file mode 100644 index b8585f26c185..000000000000 --- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2017 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.servicestests.apps.jobtestapp; - -import android.annotation.TargetApi; -import android.app.job.JobParameters; -import android.app.job.JobService; -import android.content.Intent; -import android.util.Log; - -@TargetApi(24) -public class TestJobService extends JobService { - private static final String TAG = TestJobService.class.getSimpleName(); - private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp"; - public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED"; - public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED"; - public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS"; - - @Override - public boolean onStartJob(JobParameters params) { - Log.i(TAG, "Test job executing: " + params.getJobId()); - Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED); - reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStartIntent); - return true; - } - - @Override - public boolean onStopJob(JobParameters params) { - Log.i(TAG, "Test job stopped executing: " + params.getJobId()); - Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED); - reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - sendBroadcast(reportJobStopIntent); - // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job. - return false; - } -} diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 4e1c72af2727..2f29d10ec2f9 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -47,6 +47,7 @@ android_test { "flag-junit", "notification_flags_lib", "platform-test-rules", + "SettingsLib", ], libs: [ diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index db4653208f62..839cf7ce4926 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -17,6 +17,9 @@ package com.android.server; import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; @@ -32,6 +35,7 @@ import static com.android.server.UiModeManagerService.SUPPORTED_NIGHT_MODE_CUSTO import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.fail; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @@ -65,6 +69,7 @@ import static org.testng.Assert.assertThrows; import android.Manifest; import android.app.Activity; import android.app.AlarmManager; +import android.app.Flags; import android.app.IOnProjectionStateChangedListener; import android.app.IUiModeManager; import android.content.BroadcastReceiver; @@ -84,6 +89,8 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; import android.test.mock.MockContentResolver; @@ -98,6 +105,7 @@ import com.android.server.wm.WindowManagerInternal; import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -109,6 +117,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.List; +import java.util.Map; import java.util.function.Consumer; @RunWith(AndroidTestingRunner.class) @@ -159,6 +168,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { private TwilightListener mTwilightListener; private FakePermissionEnforcer mPermissionEnforcer; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( + SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + @Before public void setUp() { // The AIDL stub will use PermissionEnforcer to check permission from the caller. @@ -1437,6 +1451,51 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { verify(mInjector).startDreamWhenDockedIfAppropriate(mContext); } + private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException { + //setup + if (modeNight) { + mService.setNightMode(MODE_NIGHT_YES); + assertTrue(mUiManagerService.getConfiguration().isNightModeActive()); + } else { + mService.setNightMode(MODE_NIGHT_NO); + assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); + } + + // attention modes with expected night modes + Map<Integer, Boolean> modes = Map.of( + MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight, + MODE_ATTENTION_THEME_OVERLAY_DAY, false, + MODE_ATTENTION_THEME_OVERLAY_NIGHT, true + ); + + // test + for (int aMode : modes.keySet()) { + try { + mService.setAttentionModeThemeOverlay(aMode); + + int appliedAMode = mService.getAttentionModeThemeOverlay(); + boolean nMode = modes.get(aMode); + + assertEquals(aMode, appliedAMode); + assertEquals(isNightModeActivated(), nMode); + } catch (RemoteException e) { + fail("Error communicating with server: " + e.getMessage()); + } + } + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException { + testAttentionModeThemeOverlay(false); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException { + testAttentionModeThemeOverlay(true); + } + private void triggerDockIntent() { final Intent dockedIntent = new Intent(Intent.ACTION_DOCK_EVENT) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index 30843d222742..3797dbb97213 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -16,7 +16,8 @@ package com.android.server.notification; -import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT; +import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; @@ -121,28 +122,49 @@ public class DefaultDeviceEffectsApplierTest { verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); verify(mColorDisplayManager).setSaturationLevel(eq(0)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); - verify(mUiModeManager).setNightModeActivatedForCustomMode( - eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); } @Test - public void apply_removesPreviouslyAppliedEffects() { + public void apply_removesEffects() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(true) .build(); mApplier.apply(previousEffects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + verify(mColorDisplayManager).setSaturationLevel(eq(0)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); mApplier.apply(noEffects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); + verify(mColorDisplayManager).setSaturationLevel(eq(100)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); - verifyZeroInteractions(mColorDisplayManager, mUiModeManager); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF)); + } + + @Test + public void apply_removesOnlyPreviouslyAppliedEffects() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .build(); + mApplier.apply(previousEffects, UPDATE_ORIGIN_USER); + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + + ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); + mApplier.apply(noEffects, UPDATE_ORIGIN_USER); + + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); + verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager); } @Test @@ -150,6 +172,7 @@ public class DefaultDeviceEffectsApplierTest { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mContext.addMockSystemService(ColorDisplayManager.class, null); mContext.addMockSystemService(WallpaperManager.class, null); + mApplier = new DefaultDeviceEffectsApplier(mContext); ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) @@ -177,7 +200,7 @@ public class DefaultDeviceEffectsApplierTest { verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean()); - verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean()); + verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt()); } @Test @@ -223,8 +246,7 @@ public class DefaultDeviceEffectsApplierTest { screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); // So the effect is applied, and we stopped listening for this event. - verify(mUiModeManager).setNightModeActivatedForCustomMode( - eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); verify(mContext).unregisterReceiver(eq(screenOffReceiver)); } @@ -239,8 +261,7 @@ public class DefaultDeviceEffectsApplierTest { origin.value()); // Effect was applied, and no broadcast receiver was registered. - verify(mUiModeManager).setNightModeActivatedForCustomMode( - eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); verify(mContext, never()).registerReceiver(any(), any(), anyInt()); } @@ -256,8 +277,7 @@ public class DefaultDeviceEffectsApplierTest { origin.value()); // Effect was applied, and no broadcast receiver was registered. - verify(mUiModeManager).setNightModeActivatedForCustomMode( - eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT)); verify(mContext, never()).registerReceiver(any(), any(), anyInt()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java index 71dbc57e5065..298637266cc3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import androidx.test.filters.SmallTest; @@ -28,7 +29,6 @@ import com.android.server.wm.SensitiveContentPackages.PackageInfo; import org.junit.After; import org.junit.Test; -import java.util.Collections; import java.util.Set; /** @@ -52,18 +52,21 @@ public class SensitiveContentPackagesTest { @After public void tearDown() { - mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + mSensitiveContentPackages.clearBlockedApps(); } @Test - public void setShouldBlockScreenCaptureForApp() { - Set<PackageInfo> blockedApps = + public void addBlockScreenCaptureForApps() { + ArraySet<PackageInfo> blockedApps = new ArraySet( Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), new PackageInfo(APP_PKG_1, APP_UID_2), new PackageInfo(APP_PKG_2, APP_UID_1), - new PackageInfo(APP_PKG_2, APP_UID_2)); + new PackageInfo(APP_PKG_2, APP_UID_2) + )); - mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps); + boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + + assertTrue(modified); assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); @@ -79,15 +82,93 @@ public class SensitiveContentPackagesTest { } @Test - public void setShouldBlockScreenCaptureForApp_empty() { - Set<PackageInfo> blockedApps = + public void addBlockScreenCaptureForApps_addedTwice() { + ArraySet<PackageInfo> blockedApps = new ArraySet( Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), new PackageInfo(APP_PKG_1, APP_UID_2), new PackageInfo(APP_PKG_2, APP_UID_1), - new PackageInfo(APP_PKG_2, APP_UID_2)); + new PackageInfo(APP_PKG_2, APP_UID_2) + )); + + mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + + assertFalse(modified); + + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); + + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); + + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + } + + @Test + public void addBlockScreenCaptureForApps_withPartialPreviousPackages() { + ArraySet<PackageInfo> blockedApps = new ArraySet( + Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), + new PackageInfo(APP_PKG_1, APP_UID_2), + new PackageInfo(APP_PKG_2, APP_UID_1), + new PackageInfo(APP_PKG_2, APP_UID_2) + )); + + mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + boolean modified = mSensitiveContentPackages + .addBlockScreenCaptureForApps( + new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1)))); + + assertTrue(modified); + + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); + + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); + + assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + } + + @Test + public void clearBlockedApps() { + ArraySet<PackageInfo> blockedApps = new ArraySet( + Set.of(new PackageInfo(APP_PKG_1, APP_UID_1), + new PackageInfo(APP_PKG_1, APP_UID_2), + new PackageInfo(APP_PKG_2, APP_UID_1), + new PackageInfo(APP_PKG_2, APP_UID_2) + )); + + mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps); + boolean modified = mSensitiveContentPackages.clearBlockedApps(); + + assertTrue(modified); + + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3)); + + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3)); + + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2)); + assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3)); + } + + @Test + public void clearBlockedApps_alreadyEmpty() { + boolean modified = mSensitiveContentPackages.clearBlockedApps(); - mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps); - mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + assertFalse(modified); assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1)); assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2)); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index a1cc8d5d9188..fe9d83776ad9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -115,7 +115,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import java.util.ArrayList; -import java.util.Collections; /** * Build/Install/Run: @@ -139,7 +138,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { @After public void tearDown() { - mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + mWm.mSensitiveContentPackages.clearBlockedApps(); } @Test @@ -824,7 +823,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void setShouldBlockScreenCaptureForApp() { + public void addBlockScreenCaptureForApps() { String testPackage = "test"; int ownerId1 = 20; int ownerId2 = 21; @@ -833,7 +832,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { blockedPackages.add(blockedPackage); WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); - wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); assertTrue(mWm.mSensitiveContentPackages .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); @@ -843,7 +842,47 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() { + public void addBlockScreenCaptureForApps_duplicate_verifyNoRefresh() { + String testPackage = "test"; + int ownerId1 = 20; + int ownerId2 = 21; + PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage); + + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + + verify(mWm, times(1)).refreshScreenCaptureDisabled(); + } + + @Test + public void addBlockScreenCaptureForApps_notDuplicate_verifyRefresh() { + String testPackage = "test"; + int ownerId1 = 20; + int ownerId2 = 21; + PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1); + PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId2); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage); + ArraySet<PackageInfo> blockedPackages2 = new ArraySet(); + blockedPackages2.add(blockedPackage); + blockedPackages2.add(blockedPackage2); + + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + wmInternal.addBlockScreenCaptureForApps(blockedPackages2); + + assertTrue(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); + assertTrue(mWm.mSensitiveContentPackages + .shouldBlockScreenCaptureForApp(testPackage, ownerId2)); + verify(mWm, times(2)).refreshScreenCaptureDisabled(); + } + + @Test + public void clearBlockedApps_clearsCache() { String testPackage = "test"; int ownerId1 = 20; PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1); @@ -851,8 +890,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { blockedPackages.add(blockedPackage); WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); - wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages); - wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + wmInternal.clearBlockedApps(); assertFalse(mWm.mSensitiveContentPackages .shouldBlockScreenCaptureForApp(testPackage, ownerId1)); @@ -860,6 +899,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + public void clearBlockedApps_alreadyEmpty_verifyNoRefresh() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.clearBlockedApps(); + + verify(mWm, never()).refreshScreenCaptureDisabled(); + } + + @Test public void testisLetterboxBackgroundMultiColored() { assertThat(setupLetterboxConfigurationWithBackgroundType( LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index fb4edfacb8e3..a0562aa2f710 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -136,7 +136,7 @@ public class WindowStateTests extends WindowTestsBase { @After public void tearDown() { - mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet()); + mWm.mSensitiveContentPackages.clearBlockedApps(); } @Test @@ -1398,7 +1398,7 @@ public class WindowStateTests extends WindowTestsBase { PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1); ArraySet<PackageInfo> blockedPackages = new ArraySet(); blockedPackages.add(blockedPackage); - mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages); + mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages); assertTrue(window1.isSecureLocked()); assertFalse(window2.isSecureLocked()); diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java index 336bfdd0fb14..a8fd6f29f862 100644 --- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java +++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java @@ -35,11 +35,13 @@ import android.util.Slog; import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.Keep; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.RingBuffer; import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType; +import java.util.function.IntFunction; +import java.util.function.Supplier; + public class BroadcastResponseStatsLogger { private static final int MAX_LOG_SIZE = @@ -49,10 +51,10 @@ public class BroadcastResponseStatsLogger { @GuardedBy("mLock") private final LogBuffer mBroadcastEventsBuffer = new LogBuffer( - BroadcastEvent.class, MAX_LOG_SIZE); + BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE); @GuardedBy("mLock") private final LogBuffer mNotificationEventsBuffer = new LogBuffer( - NotificationEvent.class, MAX_LOG_SIZE); + NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE); void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, UserHandle targetUser, long idForResponseEvent, @@ -96,8 +98,8 @@ public class BroadcastResponseStatsLogger { private static final class LogBuffer<T extends Data> extends RingBuffer<T> { - LogBuffer(Class<T> classType, int capacity) { - super(classType, capacity); + LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) { + super(newItem, newBacking, capacity); } void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage, @@ -179,8 +181,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class BroadcastEvent implements Data { + private static final class BroadcastEvent implements Data { public int sourceUid; public int targetUserId; public int targetUidProcessState; @@ -200,8 +201,7 @@ public class BroadcastResponseStatsLogger { } } - @Keep - public static final class NotificationEvent implements Data { + private static final class NotificationEvent implements Data { public int type; public String packageName; public int userId; @@ -218,7 +218,7 @@ public class BroadcastResponseStatsLogger { } } - public interface Data { + private interface Data { void reset(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java index 1df7012c44f8..49ad46131b0d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java @@ -62,7 +62,8 @@ public class PhoneCallStateHandler { SubscriptionManager subscriptionManager, TelephonyManager telephonyManager, Callback callback) { - mSubscriptionManager = Objects.requireNonNull(subscriptionManager); + mSubscriptionManager = Objects.requireNonNull(subscriptionManager) + .createForAllUserProfiles(); mTelephonyManager = Objects.requireNonNull(telephonyManager); mCallback = Objects.requireNonNull(callback); mSubscriptionManager.addOnSubscriptionsChangedListener( diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 57b13e960093..e81f48280e46 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1049,8 +1049,17 @@ public class TelecomManager { public static final int PRESENTATION_UNAVAILABLE = 5; + /** + * Controls audio route for video calls. + * 0 - Use the default audio routing strategy. + * 1 - Disable the speaker. Route the audio to Headset or Bluetooth + * or Earpiece, based on the default audio routing strategy. + * @hide + */ + public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output"; + /* - * Values for the adb property "persist.radio.videocall.audio.output" + * Values for the adb property "persist.radio.call.audio.output" */ /** @hide */ public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0; diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 73c26a3e5fc9..1badf674c8ce 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9694,6 +9694,27 @@ public class CarrierConfigManager { "remove_satellite_plmn_in_manual_network_scan_bool"; /** + * An integer key holds the time interval for refreshing or re-querying the satellite + * entitlement status from the entitlement server to ensure it is the latest. + * + * The default value is 30 days (1 month). + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = + "satellite_entitlement_status_refresh_days_int"; + + /** + * This configuration enables device to query the entitlement server to get the satellite + * configuration. + * This will need agreement the carrier before enabling this flag. + * + * The default value is false. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = + "satellite_entitlement_supported_bool"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -10799,6 +10820,8 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT, CellSignalStrengthLte.USE_RSRP); sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30); + sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 3b0397b6e480..70047a6feb9c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -928,10 +928,19 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; + /** + * Satellite communication restricted by entitlement server. This can be triggered based on + * the EntitlementStatus value received from the entitlement server to enable or disable + * satellite. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; + /** @hide */ @IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = { SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER, - SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION + SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION, + SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteCommunicationRestrictionReason {} diff --git a/test-base/Android.bp b/test-base/Android.bp index 527159a78ebf..70a95400bd9e 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -120,12 +120,13 @@ filegroup { path: "src", } -// Make the current.txt available for use by the cts/tests/signature tests. +// Make the current.txt available for use by the cts/tests/signature and /vendor tests. // ======================================================================== filegroup { name: "android-test-base-current.txt", visibility: [ "//cts/tests/signature/api", + "//vendor:__subpackages__", ], srcs: [ "api/current.txt", diff --git a/test-mock/Android.bp b/test-mock/Android.bp index 2ff74132ffbb..f37d2d17973e 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -77,12 +77,13 @@ android_ravenwood_test { auto_gen_config: true, } -// Make the current.txt available for use by the cts/tests/signature tests. +// Make the current.txt available for use by the cts/tests/signature and /vendor tests. // ======================================================================== filegroup { name: "android-test-mock-current.txt", visibility: [ "//cts/tests/signature/api", + "//vendor:__subpackages__", ], srcs: [ "api/current.txt", diff --git a/test-runner/Android.bp b/test-runner/Android.bp index 13a5dac9eb38..21e09d3221ce 100644 --- a/test-runner/Android.bp +++ b/test-runner/Android.bp @@ -79,12 +79,13 @@ java_library { ], } -// Make the current.txt available for use by the cts/tests/signature tests. +// Make the current.txt available for use by the cts/tests/signature and /vendor tests. // ======================================================================== filegroup { name: "android-test-runner-current.txt", visibility: [ "//cts/tests/signature/api", + "//vendor:__subpackages__", ], srcs: [ "api/current.txt", diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index f05611048caa..1a82021bce71 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -381,9 +381,9 @@ class PackageFlattener { return true; } - bool FlattenTypeSpec(const ResourceTableTypeView& type, - const std::vector<ResourceTableEntryView>& sorted_entries, - BigBuffer* buffer) { + ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type, + const std::vector<ResourceTableEntryView>& sorted_entries, + BigBuffer* buffer) { ChunkWriter type_spec_writer(buffer); ResTable_typeSpec* spec_header = type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE); @@ -391,7 +391,7 @@ class PackageFlattener { if (sorted_entries.empty()) { type_spec_writer.Finish(); - return true; + return spec_header; } // We can't just take the size of the vector. There may be holes in the @@ -427,7 +427,7 @@ class PackageFlattener { } } type_spec_writer.Finish(); - return true; + return spec_header; } bool FlattenTypes(BigBuffer* buffer) { @@ -450,7 +450,8 @@ class PackageFlattener { expected_type_id++; type_pool_.MakeRef(type.named_type.to_string()); - if (!FlattenTypeSpec(type, type.entries, buffer)) { + const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer); + if (!type_spec_header) { return false; } @@ -511,6 +512,10 @@ class PackageFlattener { return false; } } + + // And now we can update the type entries count in the typeSpec header. + type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>( + config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max()))); } return true; } diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java new file mode 100644 index 000000000000..379c4ae8a059 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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.okhttp.internalandroidapi; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +/** + * A domain name service that resolves IP addresses for host names. + * @hide + * @hide This class is not part of the Android public SDK API + */ +public interface Dns { + /** + * Returns the IP addresses of {@code hostname}, in the order they should + * be attempted. + * + * @hide + */ + List<InetAddress> lookup(String hostname) throws UnknownHostException; +} |