diff options
155 files changed, 1894 insertions, 741 deletions
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig index 788e82407926..2c1a8532568c 100644 --- a/apex/jobscheduler/framework/aconfig/job.aconfig +++ b/apex/jobscheduler/framework/aconfig/job.aconfig @@ -9,6 +9,7 @@ flag { flag { name: "job_debug_info_apis" + is_exported: true namespace: "backstage_power" description: "Add APIs to let apps attach debug information to jobs" bug: "293491637" @@ -16,6 +17,7 @@ flag { flag { name: "backup_jobs_exemption" + is_exported: true namespace: "backstage_power" description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content." bug: "318731461" diff --git a/core/api/current.txt b/core/api/current.txt index 8a61f4a14b50..93c34cd5e5ec 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10731,6 +10731,7 @@ package android.content { field public static final String DROPBOX_SERVICE = "dropbox"; field public static final String EUICC_SERVICE = "euicc"; field public static final String FILE_INTEGRITY_SERVICE = "file_integrity"; + field public static final String FINGERPRINT_SERVICE = "fingerprint"; field public static final String GAME_SERVICE = "game"; field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection"; field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties"; @@ -20387,6 +20388,54 @@ package android.hardware.display { } +package android.hardware.fingerprint { + + @Deprecated public class FingerprintManager { + method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); + method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); + method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); + field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 + field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 + field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 + field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 + field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 + field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc + field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 + field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb + field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 + field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 + field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 + field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa + field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 + } + + @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { + ctor @Deprecated public FingerprintManager.AuthenticationCallback(); + method @Deprecated public void onAuthenticationError(int, CharSequence); + method @Deprecated public void onAuthenticationFailed(); + method @Deprecated public void onAuthenticationHelp(int, CharSequence); + method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult); + } + + @Deprecated public static class FingerprintManager.AuthenticationResult { + method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject(); + } + + @Deprecated public static final class FingerprintManager.CryptoObject { + ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature); + ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); + ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); + method @Deprecated public javax.crypto.Cipher getCipher(); + method @Deprecated public javax.crypto.Mac getMac(); + method @Deprecated public java.security.Signature getSignature(); + } + +} + package android.hardware.input { public final class HostUsiVersion implements android.os.Parcelable { diff --git a/core/api/removed.txt b/core/api/removed.txt index c61f16333fe8..3c7c0d6e6ea1 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -35,7 +35,6 @@ package android.content { method @Deprecated @Nullable public String getFeatureId(); method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int); method public abstract java.io.File getSharedPreferencesPath(String); - field public static final String FINGERPRINT_SERVICE = "fingerprint"; } public class ContextWrapper extends android.content.Context { @@ -146,54 +145,6 @@ package android.hardware { } -package android.hardware.fingerprint { - - @Deprecated public class FingerprintManager { - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); - method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); - method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); - field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 - field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 - field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 - field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 - field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 - field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 - field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc - field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 - field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 - field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 - field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb - field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 - field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 - field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa - field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 - } - - @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { - ctor public FingerprintManager.AuthenticationCallback(); - method public void onAuthenticationError(int, CharSequence); - method public void onAuthenticationFailed(); - method public void onAuthenticationHelp(int, CharSequence); - method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult); - } - - @Deprecated public static class FingerprintManager.AuthenticationResult { - method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject(); - } - - @Deprecated public static final class FingerprintManager.CryptoObject { - ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature); - ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher); - ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac); - method public javax.crypto.Cipher getCipher(); - method public javax.crypto.Mac getMac(); - method public java.security.Signature getSignature(); - } - -} - package android.media { public final class AudioFormat implements android.os.Parcelable { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a2b847e0fb5f..a76aa6743bc5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1721,6 +1721,15 @@ package android.hardware.display { } +package android.hardware.fingerprint { + + @Deprecated public class FingerprintManager { + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int); + method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties(); + } + +} + package android.hardware.hdmi { public final class HdmiControlServiceWrapper { diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index 2e44176f342e..d802177e249b 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1,10 +1 @@ // Signature format: 2.0 -package android.hardware.fingerprint { - - @Deprecated public class FingerprintManager { - method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int); - method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties(); - } - -} - diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 350b1edf2129..b9aa18c0211c 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -3,6 +3,7 @@ package: "android.app" flag { namespace: "system_performance" name: "app_start_info" + is_exported: true description: "Control collecting of ApplicationStartInfo records and APIs." bug: "247814855" } @@ -10,6 +11,7 @@ flag { flag { namespace: "backstage_power" name: "get_binding_uid_importance" + is_exported: true description: "API to get importance of UID that's binding to the caller" bug: "292533010" } @@ -17,6 +19,7 @@ flag { flag { namespace: "backstage_power" name: "app_restrictions_api" + is_exported: true description: "API to track and query restrictions applied to apps" bug: "320150834" } @@ -24,6 +27,7 @@ flag { flag { namespace: "backstage_power" name: "uid_importance_listener_for_uids" + is_exported: true description: "API to add OnUidImportanceListener with targetted UIDs" bug: "286258140" } @@ -31,12 +35,14 @@ flag { flag { namespace: "backstage_power" name: "introduce_new_service_ontimeout_callback" + is_exported: true description: "Add a new callback in Service to indicate a FGS has reached its timeout." bug: "317799821" } flag { name: "bcast_event_timestamps" + is_exported: true namespace: "backstage_power" description: "Add APIs for clients to provide broadcast event trigger timestamps" bug: "325136414" diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 3ec6fe7728e5..4fa45be57a11 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -5,6 +5,7 @@ package: "android.app.admin.flags" flag { name: "policy_engine_migration_v2_enabled" + is_exported: true namespace: "enterprise" description: "V2 of the policy engine migrations for Android V" bug: "289520697" @@ -12,6 +13,7 @@ flag { flag { name: "device_policy_size_tracking_enabled" + is_exported: true namespace: "enterprise" description: "Add feature to track the total policy size and have a max threshold - public API changes" bug: "281543351" @@ -26,6 +28,7 @@ flag { flag { name: "onboarding_bugreport_v2_enabled" + is_exported: true namespace: "enterprise" description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports." bug: "302517677" @@ -47,6 +50,7 @@ flag { flag { name: "dedicated_device_control_api_enabled" + is_exported: true namespace: "enterprise" description: "(API) Allow the device management role holder to control which platform features are available on dedicated devices." bug: "281964214" @@ -54,6 +58,7 @@ flag { flag { name: "permission_migration_for_zero_trust_api_enabled" + is_exported: true namespace: "enterprise" description: "(API) Migrate existing APIs to permission based, and enable DMRH to call them to collect Zero Trust signals." bug: "289520697" @@ -68,6 +73,7 @@ flag { flag { name: "device_theft_api_enabled" + is_exported: true namespace: "enterprise" description: "Add new API for theft detection." bug: "325073410" @@ -89,6 +95,7 @@ flag { flag { name: "security_log_v2_enabled" + is_exported: true namespace: "enterprise" description: "Improve access to security logging in the context of Zero Trust." bug: "295324350" @@ -103,6 +110,7 @@ flag { flag { name: "allow_querying_profile_type" + is_exported: true namespace: "enterprise" description: "Public APIs to query if a user is a profile and what kind of profile type it is." bug: "323001115" @@ -117,6 +125,7 @@ flag { flag { name: "assist_content_user_restriction_enabled" + is_exported: true namespace: "enterprise" description: "Prevent work data leakage by sending assist content to privileged apps." bug: "322975406" @@ -134,6 +143,7 @@ flag { flag { name: "backup_service_security_log_event_enabled" + is_exported: true namespace: "enterprise" description: "Emit a security log event when DPM.setBackupServiceEnabled is called" bug: "304999634" @@ -141,6 +151,7 @@ flag { flag { name: "esim_management_enabled" + is_exported: true namespace: "enterprise" description: "Enable APIs to provision and manage eSIMs" bug: "295301164" @@ -148,6 +159,7 @@ flag { flag { name: "headless_device_owner_single_user_enabled" + is_exported: true namespace: "enterprise" description: "Add Headless DO support." bug: "289515470" @@ -155,6 +167,7 @@ flag { flag { name: "is_mte_policy_enforced" + is_exported: true namespace: "enterprise" description: "Allow to query whether MTE is enabled or not to check for compliance for enterprise policy" bug: "322777918" diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig index 4473b9523f1b..5f3bb0745b08 100644 --- a/core/java/android/app/background_install_control_manager.aconfig +++ b/core/java/android/app/background_install_control_manager.aconfig @@ -3,6 +3,7 @@ package: "android.app" flag { namespace: "preload_safety" name: "bic_client" + is_exported: true description: "System API for background install control." is_fixed_read_only: true bug: "287507984" diff --git a/core/java/android/app/grammatical_inflection_manager.aconfig b/core/java/android/app/grammatical_inflection_manager.aconfig index 68d12ba75560..0d7bf65215a0 100644 --- a/core/java/android/app/grammatical_inflection_manager.aconfig +++ b/core/java/android/app/grammatical_inflection_manager.aconfig @@ -2,6 +2,7 @@ package: "android.app" flag { name: "system_terms_of_address_enabled" + is_exported: true namespace: "globalintl" description: "Feature flag for System Terms of Address" bug: "297798866" diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig index ab00891b9b31..dbf3173a4ee6 100644 --- a/core/java/android/app/multitasking.aconfig +++ b/core/java/android/app/multitasking.aconfig @@ -2,6 +2,7 @@ package: "android.app" flag { name: "enable_pip_ui_state_callback_on_entering" + is_exported: true namespace: "multitasking" description: "Enables PiP UI state callback on entering" bug: "303718131" diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 274d02a79270..e9a746022a75 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -2,6 +2,7 @@ package: "android.app" flag { name: "modes_api" + is_exported: true namespace: "systemui" description: "This flag controls new and updated DND apis" bug: "300477976" @@ -16,6 +17,7 @@ flag { flag { name: "api_tvextender" + is_exported: true namespace: "systemui" description: "Guards new android.app.Notification.TvExtender api" bug: "308164892" @@ -24,6 +26,7 @@ flag { flag { name: "lifetime_extension_refactor" + is_exported: true namespace: "systemui" description: "Enables moving notification lifetime extension management from SystemUI to " "Notification Manager Service" @@ -46,6 +49,7 @@ flag { flag { name: "category_voicemail" + is_exported: true namespace: "wear_sysui" description: "Adds a new voicemail category for notifications" bug: "322806700" @@ -53,6 +57,7 @@ flag { flag { name: "notification_channel_vibration_effect_api" + is_exported: true namespace: "systemui" description: "This flag enables the API to allow setting VibrationEffect for NotificationChannels" bug: "241732519" diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig index 44f33298b1b2..dd9210faa10c 100644 --- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig +++ b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig @@ -2,6 +2,7 @@ package: "android.app.ondeviceintelligence.flags" flag { name: "enable_on_device_intelligence" + is_exported: true namespace: "ondeviceintelligence" description: "Make methods on OnDeviceIntelligenceManager available for local inference." bug: "304755128" diff --git a/core/java/android/app/pinner-client.aconfig b/core/java/android/app/pinner-client.aconfig index b60ad9ee1f8d..0f7fa14d9b6a 100644 --- a/core/java/android/app/pinner-client.aconfig +++ b/core/java/android/app/pinner-client.aconfig @@ -3,6 +3,7 @@ package: "android.app" flag { namespace: "system_performance" name: "pinner_service_client_api" + is_exported: true description: "Control exposing PinnerService APIs." bug: "307594624" }
\ No newline at end of file diff --git a/core/java/android/app/smartspace/flags.aconfig b/core/java/android/app/smartspace/flags.aconfig index 12af888bfaa5..e90ba67fe6dd 100644 --- a/core/java/android/app/smartspace/flags.aconfig +++ b/core/java/android/app/smartspace/flags.aconfig @@ -2,6 +2,7 @@ package: "android.app.smartspace.flags" flag { name: "remote_views" + is_exported: true namespace: "sysui_integrations" description: "Flag to enable the FlaggedApi to include RemoteViews in SmartspaceTarget" bug: "300157758" @@ -9,6 +10,7 @@ flag { flag { name: "access_smartspace" + is_exported: true namespace: "sysui_integrations" description: "Flag to enable the ACCESS_SMARTSPACE check in SmartspaceManagerService" bug: "297207196" diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index 4d9d911ed563..9a2d2e5d8319 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -2,6 +2,7 @@ package: "android.app.usage" flag { name: "user_interaction_type_api" + is_exported: true namespace: "backstage_power" description: "Feature flag for user interaction event report/query API" bug: "296061232" @@ -9,6 +10,7 @@ flag { flag { name: "report_usage_stats_permission" + is_exported: true namespace: "backstage_power" description: "Feature flag for the new REPORT_USAGE_STATS permission." bug: "296056771" @@ -31,6 +33,7 @@ flag { flag { name: "filter_based_event_query_api" + is_exported: true namespace: "backstage_power" description: " Feature flag to support filter based event query API" bug: "194321117" @@ -38,6 +41,7 @@ flag { flag { name: "get_app_bytes_by_data_type_api" + is_exported: true namespace: "system_performance" description: "Feature flag for collecting app data size by file type API" bug: "294088945" diff --git a/core/java/android/app/wearable/flags.aconfig b/core/java/android/app/wearable/flags.aconfig index b4f628ffc9b3..d1d7b5d85e2d 100644 --- a/core/java/android/app/wearable/flags.aconfig +++ b/core/java/android/app/wearable/flags.aconfig @@ -2,6 +2,7 @@ package: "android.app.wearable" flag { name: "enable_unsupported_operation_status_code" + is_exported: true namespace: "machine_learning" description: "This flag enables the WearableSensingManager#STATUS_UNSUPPORTED_OPERATION status code API." bug: "301427767" @@ -9,6 +10,7 @@ flag { flag { name: "enable_data_request_observer_api" + is_exported: true namespace: "machine_learning" description: "This flag enables the API to register a data request observer on WearableSensingManager." bug: "301427767" @@ -16,6 +18,7 @@ flag { flag { name: "enable_provide_wearable_connection_api" + is_exported: true namespace: "machine_learning" description: "This flag enables the WearableSensingManager#provideWearableConnection API." bug: "301427767" @@ -30,6 +33,7 @@ flag { flag { name: "enable_hotword_wearable_sensing_api" + is_exported: true namespace: "machine_learning" description: "This flag enables the APIs related to hotword in WearableSensingManager and WearableSensingService." bug: "310055381" diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 822f02f70562..451195478760 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -2,6 +2,7 @@ package: "android.appwidget.flags" flag { name: "generated_previews" + is_exported: true namespace: "app_widgets" description: "Enable support for generated previews in AppWidgetManager" bug: "306546610" @@ -26,6 +27,7 @@ flag { flag { name: "draw_data_parcel" + is_exported: true namespace: "app_widgets" description: "Enable support for transporting draw instructions as data parcel" bug: "286130467" diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig index d634b64b1a4e..ecc5e1bd194f 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -2,6 +2,7 @@ package: "android.companion" flag { name: "new_association_builder" + is_exported: true namespace: "companion" description: "Controls if the new Builder is exposed to test apis." bug: "296251481" @@ -16,6 +17,7 @@ flag { flag { name: "association_tag" + is_exported: true namespace: "companion" description: "Enable Association tag APIs " bug: "289241123" @@ -23,6 +25,7 @@ flag { flag { name: "device_presence" + is_exported: true namespace: "companion" description: "Enable device presence APIs" bug: "283000075" @@ -30,6 +33,7 @@ flag { flag { name: "perm_sync_user_consent" + is_exported: true namespace: "companion" description: "Expose perm sync user consent API" bug: "309528663" diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 588e4fce1f3d..a6a4f5e77515 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -19,6 +19,7 @@ flag { flag { name: "dynamic_policy" + is_exported: true namespace: "virtual_devices" description: "Enable dynamic policy API" bug: "298401780" @@ -26,6 +27,7 @@ flag { flag { name: "cross_device_clipboard" + is_exported: true namespace: "virtual_devices" description: "Enable cross-device clipboard API" bug: "306622082" @@ -40,6 +42,7 @@ flag { flag { name: "vdm_custom_ime" + is_exported: true namespace: "virtual_devices" description: "Enable custom IME API" bug: "287269288" @@ -47,6 +50,7 @@ flag { flag { name: "vdm_custom_home" + is_exported: true namespace: "virtual_devices" description: "Enable custom home API" bug: "297168328" @@ -54,6 +58,7 @@ flag { flag { name: "vdm_public_apis" + is_exported: true namespace: "virtual_devices" description: "Enable public VDM API for device capabilities" bug: "297253526" @@ -61,6 +66,7 @@ flag { flag { name: "virtual_camera" + is_exported: true namespace: "virtual_devices" description: "Enable Virtual Camera" bug: "270352264" @@ -82,6 +88,7 @@ flag { flag { name: "persistent_device_id_api" + is_exported: true namespace: "virtual_devices" description: "Enable persistent device ID notification API" bug: "295258915" @@ -96,6 +103,7 @@ flag { flag { name: "interactive_screen_mirror" + is_exported: true namespace: "virtual_devices" description: "Enable interactive screen mirroring using Virtual Devices" bug: "292212199" @@ -103,6 +111,7 @@ flag { flag { name: "virtual_stylus" + is_exported: true namespace: "virtual_devices" description: "Enable virtual stylus input" bug: "304829446" diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 284e3184d436..c0c91cbdbc35 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5067,7 +5067,6 @@ public abstract class Context { * {@link android.hardware.fingerprint.FingerprintManager} for handling management * of fingerprints. * - * @removed See {@link android.hardware.biometrics.BiometricPrompt} * @see #getSystemService(String) * @see android.hardware.fingerprint.FingerprintManager */ diff --git a/core/java/android/content/flags/flags.aconfig b/core/java/android/content/flags/flags.aconfig index 3445fb53d307..27bce5bb83dd 100644 --- a/core/java/android/content/flags/flags.aconfig +++ b/core/java/android/content/flags/flags.aconfig @@ -2,6 +2,7 @@ package: "android.content.flags" flag { name: "enable_bind_package_isolated_process" + is_exported: true namespace: "machine_learning" description: "This flag enables the newly added flag for binding package-private isolated processes." bug: "312706530" diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 92cb9cc1d8dc..cde565b3f66e 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -2,6 +2,7 @@ package: "android.content.pm" flag { name: "quarantined_enabled" + is_exported: true namespace: "package_manager_service" description: "Feature flag for Quarantined state" bug: "269127435" @@ -9,6 +10,7 @@ flag { flag { name: "archiving" + is_exported: true namespace: "package_manager_service" description: "Feature flag to enable the archiving feature." bug: "278553670" @@ -24,6 +26,7 @@ flag { flag { name: "stay_stopped" + is_exported: true namespace: "backstage_power" description: "Feature flag to improve stopped state enforcement" bug: "296644915" @@ -39,6 +42,7 @@ flag { flag { name: "get_package_info" + is_exported: true namespace: "package_manager_service" description: "Feature flag to enable the feature to retrieve package info without installation." bug: "269149275" @@ -54,6 +58,7 @@ flag { flag { name: "sdk_lib_independence" + is_exported: true namespace: "package_manager_service" description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable." bug: "295827951" @@ -78,6 +83,7 @@ flag { flag { name: "get_resolved_apk_path" + is_exported: true namespace: "package_manager_service" description: "Feature flag to retrieve resolved path of the base APK during an app install." bug: "269728874" @@ -92,6 +98,7 @@ flag { flag { name: "read_install_info" + is_exported: true namespace: "package_manager_service" description: "Feature flag to read install related information from an APK." bug: "275658500" @@ -113,6 +120,7 @@ flag { flag { name: "relative_reference_intent_filters" + is_exported: true namespace: "package_manager_service" description: "Feature flag to enable relative reference intent filters" bug: "307556883" @@ -121,6 +129,7 @@ flag { flag { name: "fix_duplicated_flags" + is_exported: true namespace: "package_manager_service" description: "Feature flag to fix duplicated PackageManager flag values" bug: "314815969" @@ -128,6 +137,7 @@ flag { flag { name: "provide_info_of_apk_in_apex" + is_exported: true namespace: "package_manager_service" description: "Feature flag to provide the information of APK-in-APEX" bug: "306329516" @@ -144,6 +154,7 @@ flag { flag { name: "introduce_media_processing_type" + is_exported: true namespace: "backstage_power" description: "Add a new FGS type for media processing use cases." bug: "317788011" @@ -182,6 +193,7 @@ flag { flag { name: "emergency_install_permission" + is_exported: true namespace: "permissions" description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES" bug: "321080601" @@ -189,6 +201,7 @@ flag { flag { name: "asl_in_apk_app_metadata_source" + is_exported: true namespace: "package_manager_service" description: "Feature flag to allow to know if the Android Safety Label (ASL) of an app is provided by the app's APK itself, or provided by an installer." bug: "287487923" @@ -205,6 +218,7 @@ flag { flag { name: "set_pre_verified_domains" + is_exported: true namespace: "package_manager_service" description: "Feature flag to enable pre-verified domains" bug: "307327678" diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 2d32aed5a1ad..4963a4f27803 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -24,6 +24,7 @@ flag { flag { name: "support_communal_profile" + is_exported: true namespace: "multiuser" description: "Framework support for communal profile." bug: "285426179" @@ -31,6 +32,7 @@ flag { flag { name: "support_communal_profile_nextgen" + is_exported: true namespace: "multiuser" description: "Further framework support for communal profile, beyond the basics, for later releases." bug: "285426179" @@ -59,6 +61,7 @@ flag { flag { name: "enable_biometrics_to_unlock_private_space" + is_exported: true namespace: "profile_experiences" description: "Add support to unlock the private space using biometrics" bug: "312184187" @@ -102,6 +105,7 @@ flag { flag { name: "enable_system_user_only_for_services_and_providers" + is_exported: true namespace: "multiuser" description: "Enable systemUserOnly manifest attribute for services and providers." bug: "302354856" @@ -118,6 +122,7 @@ flag { flag { name: "enable_permission_to_access_hidden_profiles" + is_exported: true namespace: "profile_experiences" description: "Add permission to access API hidden users data via system APIs" bug: "321988638" diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index 7fd0b03b213d..8f5c912d8c03 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -2,6 +2,7 @@ package: "android.content.res" flag { name: "default_locale" + is_exported: true namespace: "resource_manager" description: "Feature flag for default locale in LocaleConfig" bug: "117306409" @@ -11,6 +12,7 @@ flag { flag { name: "font_scale_converter_public" + is_exported: true namespace: "accessibility" description: "Enables the public API for FontScaleConverter, including enabling thread-safe caching." bug: "239736383" @@ -20,6 +22,7 @@ flag { flag { name: "asset_file_descriptor_frro" + is_exported: true namespace: "resource_manager" description: "Feature flag for passing in an AssetFileDescriptor to create an frro" bug: "304478666" @@ -27,6 +30,7 @@ flag { flag { name: "manifest_flagging" + is_exported: true namespace: "resource_manager" description: "Feature flag for flagging manifest entries" bug: "297373084" @@ -36,6 +40,7 @@ flag { flag { name: "nine_patch_frro" + is_exported: true namespace: "resource_manager" description: "Feature flag for creating an frro from a 9-patch" bug: "296324826" @@ -43,6 +48,7 @@ flag { flag { name: "register_resource_paths" + is_exported: true namespace: "resource_manager" description: "Feature flag for register resource paths for shared library" bug: "306202569" diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 16ca31f27028..d0773297a4a0 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -3,6 +3,7 @@ package: "android.credentials.flags" flag { namespace: "credential_manager" name: "settings_activity_enabled" + is_exported: true description: "Enable the Credential Manager Settings Activity APIs" bug: "300014059" } @@ -24,6 +25,7 @@ flag { flag { namespace: "credential_manager" name: "new_settings_intents" + is_exported: true description: "Enables settings intents to redirect to new settings page" bug: "307587989" } @@ -45,6 +47,7 @@ flag { flag { namespace: "credential_manager" name: "configurable_selector_ui_enabled" + is_exported: true description: "Enables OEM configurable Credential Selector UI" bug: "319448437" is_exported: true diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig index 92ef9c24c4ef..7ecffaf01549 100644 --- a/core/java/android/database/sqlite/flags.aconfig +++ b/core/java/android/database/sqlite/flags.aconfig @@ -2,6 +2,7 @@ package: "android.database.sqlite" flag { name: "sqlite_apis_35" + is_exported: true namespace: "system_performance" is_fixed_read_only: true description: "SQLite APIs held back for Android 15" diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index ff07498836af..9836eece19fe 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -18,6 +18,7 @@ flag { flag { name: "get_op_id_crypto_object" + is_exported: true namespace: "biometrics_framework" description: "Feature flag for adding a get operation id api to CryptoObject." bug: "307601768" @@ -25,8 +26,8 @@ flag { flag { name: "custom_biometric_prompt" + is_exported: true namespace: "biometrics_framework" description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder." bug: "302735104" } - diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig index 73a9e346bd5d..e474603f2b03 100644 --- a/core/java/android/hardware/devicestate/feature/flags.aconfig +++ b/core/java/android/hardware/devicestate/feature/flags.aconfig @@ -2,6 +2,7 @@ package: "android.hardware.devicestate.feature.flags" flag { name: "device_state_property_api" + is_exported: true namespace: "windowing_sdk" description: "Updated DeviceState hasProperty API" bug: "293636629" diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index b0f69f56cba7..81e321d96aa6 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -83,8 +83,7 @@ import javax.crypto.Mac; /** * A class that coordinates access to the fingerprint hardware. - * - * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting + * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting * authentication. In a world where devices may have different types of biometric authentication, * it's much more realistic to have a system-provided authentication dialog since the method may * vary by vendor/device. @@ -95,6 +94,7 @@ import javax.crypto.Mac; @RequiresFeature(PackageManager.FEATURE_FINGERPRINT) public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants { private static final String TAG = "FingerprintManager"; + private static final boolean DEBUG = true; private static final int MSG_ENROLL_RESULT = 100; private static final int MSG_ACQUIRED = 101; private static final int MSG_AUTHENTICATION_SUCCEEDED = 102; @@ -196,7 +196,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Retrieves a test session for FingerprintManager. - * * @hide */ @TestApi @@ -255,10 +254,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** - * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the * framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects. - * - * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} + * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} */ @Deprecated public static final class CryptoObject extends android.hardware.biometrics.CryptoObject { @@ -332,8 +330,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Container for callback data from {@link FingerprintManager#authenticate(CryptoObject, * CancellationSignal, int, AuthenticationCallback, Handler)}. - * - * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult} + * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult} */ @Deprecated public static class AuthenticationResult { @@ -395,8 +392,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * FingerprintManager#authenticate(CryptoObject, CancellationSignal, * int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to * fingerprint events. - * - * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} + * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback} */ @Deprecated public static abstract class AuthenticationCallback @@ -459,7 +455,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Callback structure provided for {@link #detectFingerprint(CancellationSignal, * FingerprintDetectionCallback, int, Surface)}. - * * @hide */ public interface FingerprintDetectionCallback { @@ -613,8 +608,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * by <a href="{@docRoot}training/articles/keystore.html">Android Keystore * facility</a>. * @throws IllegalStateException if the crypto primitive is not initialized. - * - * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, + * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate( * BiometricPrompt.CryptoObject, CancellationSignal, Executor, * BiometricPrompt.AuthenticationCallback)} @@ -629,7 +623,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Per-user version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. - * * @hide */ @Deprecated @@ -642,7 +635,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Per-user and per-sensor version of authenticate. * @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}. - * * @hide */ @Deprecated @@ -659,7 +651,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Version of authenticate with additional options. - * * @hide */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) @@ -707,7 +698,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Uses the fingerprint hardware to detect for the presence of a finger, without giving details * about accept/reject/lockout. - * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -750,7 +740,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @param callback an object to receive enrollment events * @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics * should be logged. - * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -821,7 +810,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first * enumerated sensor. - * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -836,7 +824,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Revokes the specified challenge. - * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -862,7 +849,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @param sensorId Sensor ID that this operation takes effect for * @param userId User ID that this operation takes effect for. * @param hardwareAuthToken An opaque token returned by password confirmation. - * * @hide */ @RequiresPermission(RESET_FINGERPRINT_LOCKOUT) @@ -900,7 +886,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Removes all fingerprint templates for the given user. - * * @hide */ @RequiresPermission(MANAGE_FINGERPRINT) @@ -1020,7 +1005,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Forwards BiometricStateListener to FingerprintService * @param listener new BiometricStateListener being added - * * @hide */ public void registerBiometricStateListener(@NonNull BiometricStateListener listener) { @@ -1172,8 +1156,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** - * This is triggered by SideFpsEventHandler. - * + * This is triggered by SideFpsEventHandler * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -1186,8 +1169,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Determine if there is at least one fingerprint enrolled. * * @return true if at least one fingerprint is enrolled, false otherwise - * - * @removed See {@link BiometricPrompt} and + * @deprecated See {@link BiometricPrompt} and * {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS} */ @Deprecated @@ -1221,8 +1203,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * Determine if fingerprint hardware is present and functional. * * @return true if hardware is present and functional, false otherwise. - * - * @removed See {@link BiometricPrompt} and + * @deprecated See {@link BiometricPrompt} and * {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE} */ @Deprecated @@ -1248,7 +1229,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Get statically configured sensor properties. - * * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) @@ -1267,7 +1247,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Returns whether the device has a power button fingerprint sensor. * @return boolean indicating whether power button is fingerprint sensor - * * @hide */ public boolean isPowerbuttonFps() { diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig index c6a352e0fedf..1165e650f469 100644 --- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig +++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig @@ -2,6 +2,7 @@ package: "android.hardware.flags" flag { name: "overlayproperties_class_api" + is_exported: true namespace: "core_graphics" description: "public OverlayProperties class, OverlayProperties#supportMixedColorSpaces and Display#getOverlaySupport API" bug: "267234573" diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index e070fe570907..9684e6498bfa 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -27,6 +27,7 @@ flag { flag { namespace: "input_native" name: "pointer_coords_is_resampled_api" + is_exported: true description: "Makes MotionEvent.PointerCoords#isResampled() a public API" bug: "298197511" } @@ -34,6 +35,7 @@ flag { flag { namespace: "input_native" name: "emoji_and_screenshot_keycodes_available" + is_exported: true description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots" bug: "315307777" } diff --git a/core/java/android/hardware/radio/flags.aconfig b/core/java/android/hardware/radio/flags.aconfig index dbc1a4b21cfb..d0d10c17ee38 100644 --- a/core/java/android/hardware/radio/flags.aconfig +++ b/core/java/android/hardware/radio/flags.aconfig @@ -2,6 +2,7 @@ package: "android.hardware.radio" flag { name: "hd_radio_improved" + is_exported: true namespace: "car_framework" description: "Feature flag for improved HD radio support with less vendor extensions" bug: "280300929" diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig index 9e487e1a4fc6..fac02ce652b2 100644 --- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig +++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig @@ -2,6 +2,7 @@ package: "android.hardware.usb.flags" flag { name: "enable_usb_data_compliance_warning" + is_exported: true namespace: "system_sw_usb" description: "Enable USB data compliance warnings when set" bug: "296119135" diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig index a4956311995c..3dd746c5fad3 100644 --- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig +++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig @@ -2,6 +2,7 @@ package: "android.hardware.usb.flags" flag { name: "enable_is_pd_compliant_api" + is_exported: true namespace: "usb" description: "Feature flag for the api to check if a port is PD compliant" bug: "323470419" @@ -9,6 +10,7 @@ flag { flag { name: "enable_is_mode_change_supported_api" + is_exported: true namespace: "usb" description: "Feature flag for the api to check if a port supports mode change" bug: "323470419" diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 375d729a4e08..311e99111d04 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -26,6 +26,7 @@ flag { flag { name: "remove_app_profiler_pss_collection" + is_exported: true namespace: "backstage_power" description: "Replaces background PSS collection in AppProfiler with RSS" bug: "297542292" @@ -33,6 +34,7 @@ flag { flag { name: "allow_thermal_headroom_thresholds" + is_exported: true namespace: "game" description: "Enable thermal headroom thresholds API" bug: "288119641" @@ -41,6 +43,7 @@ flag { # This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations. flag { name: "allow_private_profile" + is_exported: true namespace: "profile_experiences" description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion." bug: "299069460" @@ -49,6 +52,7 @@ flag { flag { name: "bugreport_mode_max_value" + is_exported: true namespace: "telephony" description: "Introduce a constant as maximum value of bugreport mode." bug: "305067125" @@ -56,6 +60,7 @@ flag { flag { name: "adpf_prefer_power_efficiency" + is_exported: true namespace: "game" description: "Guards the ADPF power efficiency API" bug: "288117936" @@ -63,6 +68,7 @@ flag { flag { name: "security_state_service" + is_exported: true namespace: "dynamic_spl" description: "Guards the Security State API." bug: "302189431" @@ -70,6 +76,7 @@ flag { flag { name: "battery_saver_supported_check_api" + is_exported: true namespace: "backstage_power" description: "Guards a new API in PowerManager to check if battery saver is supported or not." bug: "305067031" @@ -77,6 +84,7 @@ flag { flag { name: "adpf_gpu_report_actual_work_duration" + is_exported: true namespace: "game" description: "Guards the ADPF GPU APIs." bug: "284324521" @@ -114,6 +122,7 @@ flag { flag { name: "battery_part_status_api" + is_exported: true namespace: "phoenix" description: "Feature flag for adding Health HAL v3 APIs." is_fixed_read_only: true @@ -122,6 +131,7 @@ flag { flag { name: "storage_lifetime_api" + is_exported: true namespace: "phoenix" description: "Feature flag for adding storage component health APIs." is_fixed_read_only: true @@ -131,6 +141,7 @@ flag { flag { namespace: "system_performance" name: "telemetry_apis_framework_initialization" + is_exported: true description: "Control framework initialization APIs of telemetry APIs feature." is_fixed_read_only: true bug: "324241334" diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index d485eca7375b..bb0498ed6a78 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { namespace: "haptics" name: "haptics_customization_enabled" + is_exported: true description: "Enables the haptics customization feature" bug: "241918098" } diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 999bc99b6915..2710df2ec982 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -2,6 +2,7 @@ package: "android.permission.flags" flag { name: "device_aware_permission_apis_enabled" + is_exported: true is_fixed_read_only: true namespace: "permissions" description: "enable device aware permission APIs" @@ -10,6 +11,7 @@ flag { flag { name: "voice_activation_permission_apis" + is_exported: true namespace: "permissions" description: "enable voice activation permission APIs" bug: "287264308" @@ -17,6 +19,7 @@ flag { flag { name: "system_server_role_controller_enabled" + is_exported: true is_fixed_read_only: true namespace: "permissions" description: "enable role controller in system server" @@ -25,6 +28,7 @@ flag { flag { name: "set_next_attribution_source" + is_exported: true namespace: "permissions" description: "enable AttributionSource.setNextAttributionSource" bug: "304478648" @@ -32,6 +36,7 @@ flag { flag { name: "should_register_attribution_source" + is_exported: true namespace: "permissions" description: "enable the shouldRegisterAttributionSource API" bug: "305057691" @@ -39,6 +44,7 @@ flag { flag { name: "attribution_source_constructor" + is_exported: true namespace: "permissions" description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)" bug: "304478648" @@ -46,6 +52,7 @@ flag { flag { name: "enhanced_confirmation_mode_apis_enabled" + is_exported: true is_fixed_read_only: true namespace: "permissions" description: "enable enhanced confirmation mode apis" @@ -54,6 +61,7 @@ flag { flag { name: "op_enable_mobile_data_by_user" + is_exported: true namespace: "permissions" description: "enables logging of the OP_ENABLE_MOBILE_DATA_BY_USER" bug: "222650148" @@ -61,6 +69,7 @@ flag { flag { name: "factory_reset_prep_permission_apis" + is_exported: true namespace: "wallet_integration" description: "enable Permission PREPARE_FACTORY_RESET." bug: "302016478" @@ -68,6 +77,7 @@ flag { flag { name: "retail_demo_role_enabled" + is_exported: true namespace: "permissions" description: "default retail demo role holder" bug: "274132354" @@ -82,6 +92,7 @@ flag { flag { name: "wallet_role_enabled" + is_exported: true namespace: "wallet_integration" description: "This flag is used to enabled the Wallet Role for all users on the device" bug: "283989236" @@ -114,6 +125,7 @@ flag { flag { name: "get_emergency_role_holder_api_enabled" + is_exported: true is_fixed_read_only: true namespace: "permissions" description: "Enables the getEmergencyRoleHolder API." diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 25c2b0eb80d7..d0593e7398fc 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12389,6 +12389,10 @@ public final class Settings { /** * Whether or not secure windows should be disabled. This only works on debuggable builds. * + * <p>When this setting is set to a non-zero value, all windows are treated as non-secure. + * Content in windows with {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} will + * appear in screenshots and recordings. + * * @hide */ public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows"; diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig index ea1ac2793a11..9245557bd488 100644 --- a/core/java/android/provider/flags.aconfig +++ b/core/java/android/provider/flags.aconfig @@ -2,6 +2,7 @@ package: "android.provider" flag { name: "system_settings_default" + is_exported: true namespace: "package_manager_service" description: "Enable Settings.System.resetToDefault APIs." bug: "279083734" @@ -9,6 +10,7 @@ flag { flag { name: "user_keys" + is_exported: true namespace: "privacy_infra_policy" description: "This flag controls new E2EE contact keys API" bug: "290696572" @@ -16,6 +18,7 @@ flag { flag { name: "backup_tasks_settings_screen" + is_exported: true namespace: "backstage_power" description: "Add a new settings page for the RUN_BACKUP_JOBS permission." bug: "320563660" diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 3c77c44fb3f0..7f5b550c830a 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { name: "fsverity_api" + is_exported: true namespace: "hardware_backed_security" description: "Feature flag for fs-verity API" bug: "285185747" @@ -64,6 +65,7 @@ flag { flag { name: "frp_enforcement" + is_exported: true namespace: "hardware_backed_security" description: "This flag controls whether PDB enforces FRP" bug: "290312729" diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index 0bae459fefc3..548f8aa8113a 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -9,6 +9,7 @@ flag { flag { name: "asm_restrictions_enabled" + is_exported: true namespace: "responsible_apis" description: "Enables ASM restrictions for activity starts and finishes" bug: "230590090" @@ -23,6 +24,7 @@ flag { flag { name: "content_uri_permission_apis" + is_exported: true namespace: "responsible_apis" description: "Enables the content URI permission APIs" bug: "293467489" @@ -30,6 +32,7 @@ flag { flag { name: "enforce_intent_filter_match" + is_exported: true namespace: "responsible_apis" description: "Make delivered intents match components' intent filters" bug: "293560872" diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig index c7e47d4b3627..7f9764e82c5d 100644 --- a/core/java/android/service/appprediction/flags/flags.aconfig +++ b/core/java/android/service/appprediction/flags/flags.aconfig @@ -2,6 +2,7 @@ package: "android.service.appprediction.flags" flag { name: "service_features_api" + is_exported: true namespace: "systemui" description: "Guards the new requestServiceFeatures api" bug: "292565550" diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig index 00236dfa7876..a3eff3becd49 100644 --- a/core/java/android/service/chooser/flags.aconfig +++ b/core/java/android/service/chooser/flags.aconfig @@ -2,6 +2,7 @@ package: "android.service.chooser" flag { name: "chooser_album_text" + is_exported: true namespace: "intentresolver" description: "Flag controlling the album text subtype hint for sharesheet" bug: "323380224" @@ -9,6 +10,7 @@ flag { flag { name: "enable_sharesheet_metadata_extra" + is_exported: true namespace: "intentresolver" description: "This flag enables sharesheet metadata to be displayed to users." bug: "318942069" @@ -16,6 +18,7 @@ flag { flag { name: "chooser_payload_toggling" + is_exported: true namespace: "intentresolver" description: "This flag controls content toggling in Chooser" bug: "302691505" @@ -23,6 +26,7 @@ flag { flag { name: "enable_chooser_result" + is_exported: true namespace: "intentresolver" description: "Provides additional callbacks with information about user actions in ChooserResult" bug: "263474465" diff --git a/core/java/android/service/controls/flags/flags.aconfig b/core/java/android/service/controls/flags/flags.aconfig index 3a288440d362..197f1bcbc001 100644 --- a/core/java/android/service/controls/flags/flags.aconfig +++ b/core/java/android/service/controls/flags/flags.aconfig @@ -2,6 +2,7 @@ package: "android.service.controls.flags" flag { name: "home_panel_dream" + is_exported: true namespace: "systemui" description: "Enables the home controls dream feature." bug: "298025023" diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig index c5acc2ceb968..35cd3edcafcb 100644 --- a/core/java/android/service/notification/flags.aconfig +++ b/core/java/android/service/notification/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { name: "redact_sensitive_notifications_from_untrusted_listeners" + is_exported: true namespace: "systemui" description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices" bug: "306271190" @@ -18,6 +19,7 @@ flag { flag { name: "callstyle_callback_api" + is_exported: true namespace: "systemui" description: "Guards the new CallStyleNotificationEventsCallback" bug: "305095040" diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig index 22e8cddbfdb8..633304b94a5f 100644 --- a/core/java/android/service/voice/flags/flags.aconfig +++ b/core/java/android/service/voice/flags/flags.aconfig @@ -2,6 +2,7 @@ package: "android.service.voice.flags" flag { name: "allow_training_data_egress_from_hds" + is_exported: true namespace: "machine_learning" description: "This flag allows the hotword detection service to egress training data to the default assistant." bug: "296074924" @@ -9,6 +10,7 @@ flag { flag { name: "allow_hotword_bump_egress" + is_exported: true namespace: "machine_learning" description: "This flag allows hotword detection service to egress reason code for hotword bump." bug: "290951024" @@ -16,6 +18,7 @@ flag { flag { name: "allow_foreground_activities_in_on_show" + is_exported: true namespace: "machine_learning" description: "This flag allows providing foreground app component along with onShow args." bug: "319409708" @@ -23,6 +26,7 @@ flag { flag { name: "allow_various_attention_types" + is_exported: true namespace: "visual_query" description: "This flag allows visual query detection service to set different attention types." bug: "318617199" @@ -30,6 +34,7 @@ flag { flag { name: "allow_complex_results_egress_from_vqds" + is_exported: true namespace: "visual_query" description: "This flag allows visual query detection service egress detailed results. " bug: "318617199" @@ -37,6 +42,7 @@ flag { flag { name: "allow_speaker_id_egress" + is_exported: true namespace: "machine_learning" description: "This flag allows hotword detection service and visual query detection service to egress current speaker profile id." bug: "318617199" diff --git a/core/java/android/speech/flags/speech_flags.aconfig b/core/java/android/speech/flags/speech_flags.aconfig index fd8012746a27..fa3359264ab6 100644 --- a/core/java/android/speech/flags/speech_flags.aconfig +++ b/core/java/android/speech/flags/speech_flags.aconfig @@ -2,6 +2,7 @@ package: "android.speech.flags" flag { name: "multilang_extra_launch" + is_exported: true namespace: "machine_learning" description: "Feature flag for adding new extra for multi-lang feature" bug: "312489931" diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 30b1a2ef5849..8e1ac631cf03 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { name: "new_fonts_fallback_xml" + is_exported: true namespace: "text" description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" # Make read only, as it could be used before the Settings provider is initialized. @@ -26,6 +27,7 @@ flag { flag { name: "fix_line_height_for_locale" + is_exported: true namespace: "text" description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" bug: "303326708" @@ -33,6 +35,7 @@ flag { flag { name: "no_break_no_hyphenation_span" + is_exported: true namespace: "text" description: "A feature flag that adding new spans that prevents line breaking and hyphenation." bug: "283193586" @@ -57,6 +60,7 @@ flag { flag { name: "use_bounds_for_width" + is_exported: true namespace: "text" description: "Feature flag for preventing horizontal clipping." bug: "63938206" @@ -71,6 +75,7 @@ flag { flag { name: "word_style_auto" + is_exported: true namespace: "text" description: "A feature flag that implements line break word style auto." bug: "280005585" @@ -78,6 +83,7 @@ flag { flag { name: "letter_spacing_justification" + is_exported: true namespace: "text" description: "A feature flag that implement inter character justification." bug: "283193133" diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 5b99c71f3a8b..91bd4ea0bc87 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -4,6 +4,7 @@ package: "android.view.accessibility" flag { name: "a11y_overlay_callbacks" + is_exported: true namespace: "accessibility" description: "Whether to allow the passing of result callbacks when attaching a11y overlays." bug: "304478691" @@ -26,6 +27,7 @@ flag { flag { namespace: "accessibility" name: "braille_display_hid" + is_exported: true description: "Enables new APIs for an AccessibilityService to communicate with a HID Braille display" bug: "303522222" } @@ -40,6 +42,7 @@ flag { flag { namespace: "accessibility" name: "collection_info_item_counts" + is_exported: true description: "Fields for total items and the number of important for accessibility items in a collection" bug: "302376158" } @@ -61,6 +64,7 @@ flag { flag { namespace: "accessibility" name: "flash_notification_system_api" + is_exported: true description: "Makes flash notification APIs as system APIs for calling from mainline module" bug: "303131332" } @@ -74,6 +78,7 @@ flag { flag { name: "motion_event_observing" + is_exported: true namespace: "accessibility" description: "Allows accessibility services to intercept but not consume motion events from specified sources." bug: "297595990" @@ -82,6 +87,7 @@ flag { flag { namespace: "accessibility" name: "granular_scrolling" + is_exported: true description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" bug: "302376158" } @@ -103,6 +109,7 @@ flag { flag { namespace: "accessibility" name: "add_type_window_control" + is_exported: true description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations" bug: "320445550" } diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig index 5d3153c00e8a..4de0f29c60fe 100644 --- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -23,6 +23,7 @@ flag { flag { name: "create_accessibility_overlay_app_op_enabled" + is_exported: true namespace: "content_protection" description: "If true, an appop is logged on creation of accessibility overlays." bug: "289081465" @@ -30,6 +31,7 @@ flag { flag { name: "rapid_clear_notifications_by_listener_app_op_enabled" + is_exported: true namespace: "content_protection" description: "If true, an appop is logged when a notification is rapidly cleared by a notification listener." bug: "289080543" @@ -37,6 +39,7 @@ flag { flag { name: "manage_device_policy_enabled" + is_exported: true namespace: "content_protection" description: "If true, the APIs to manage content protection device policy will be enabled." bug: "319477846" diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 05cabd56f532..06598b3dfdbd 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -2,6 +2,7 @@ package: "android.view.flags" flag { name: "view_velocity_api" + is_exported: true namespace: "toolkit" description: "Feature flag for view content velocity api" bug: "293513816" @@ -16,6 +17,7 @@ flag { flag { name: "toolkit_set_frame_rate_read_only" + is_exported: true namespace: "toolkit" description: "Feature flag for toolkit to set frame rate" bug: "293512962" @@ -24,6 +26,7 @@ flag { flag { name: "expected_presentation_time_api" + is_exported: true namespace: "toolkit" description: "Feature flag for using expected presentation time of the Choreographer" bug: "278730197" @@ -31,6 +34,7 @@ flag { flag { name: "expected_presentation_time_read_only" + is_exported: true namespace: "toolkit" description: "Feature flag for using expected presentation time of the Choreographer" bug: "278730197" diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig index d1d871c2dbda..a7c41046b5b4 100644 --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig @@ -3,6 +3,7 @@ package: "android.view.flags" flag { namespace: "toolkit" name: "scroll_feedback_api" + is_exported: true description: "Enable the scroll feedback APIs" bug: "239594271" } diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index 6cf89d685963..c482f8be7315 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -28,6 +28,7 @@ flag { flag { name: "sensitive_content_app_protection_api" + is_exported: true namespace: "permissions" description: "This flag controls the new sensitive content protection API," " The API will be used by other ui toolkits (i.e. compose, webview, custom virtual views)." diff --git a/core/java/android/view/flags/window_insets.aconfig b/core/java/android/view/flags/window_insets.aconfig index 201b7ad62f14..bf6df5ca21cf 100644 --- a/core/java/android/view/flags/window_insets.aconfig +++ b/core/java/android/view/flags/window_insets.aconfig @@ -2,6 +2,7 @@ package: "android.view.flags" flag { name: "customizable_window_headers" + is_exported: true namespace: "lse_desktop_experience" description: "Flag to control the caption bar appearance and to fit app content in its empty space" bug: "316387515" diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index 8d3920f8b1da..be74a65046af 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { name: "editorinfo_handwriting_enabled" + is_exported: true namespace: "input_method" description: "Feature flag for adding EditorInfo#mStylusHandwritingEnabled" bug: "293898187" @@ -18,6 +19,7 @@ flag { flag { name: "imm_userhandle_hostsidetests" + is_exported: true namespace: "input_method" description: "Feature flag for replacing UserIdInt with UserHandle in some helper IMM functions" bug: "301713309" @@ -26,6 +28,7 @@ flag { flag { name: "concurrent_input_methods" + is_exported: true namespace: "input_method" description: "Feature flag for concurrent multi-session IME" bug: "284527000" @@ -34,6 +37,7 @@ flag { flag { name: "home_screen_handwriting_delegator" + is_exported: true namespace: "input_method" description: "Feature flag for supporting stylus handwriting delegation from RemoteViews on the home screen" bug: "279959705" @@ -49,6 +53,7 @@ flag { flag { name: "use_zero_jank_proxy" + is_exported: true namespace: "input_method" description: "Feature flag for using a proxy that uses async calls to achieve zero jank for IMMS calls." bug: "293640003" @@ -57,6 +62,7 @@ flag { flag { name: "ime_switcher_revamp" + is_exported: true namespace: "input_method" description: "Feature flag for revamping the Input Method Switcher menu" bug: "311791923" @@ -73,6 +79,7 @@ flag { flag { name: "connectionless_handwriting" + is_exported: true namespace: "input_method" description: "Feature flag for connectionless stylus handwriting APIs" bug: "300979854" diff --git a/core/java/android/webkit/flags.aconfig b/core/java/android/webkit/flags.aconfig index 6938b29e78e9..2d834a8b2384 100644 --- a/core/java/android/webkit/flags.aconfig +++ b/core/java/android/webkit/flags.aconfig @@ -2,6 +2,7 @@ package: "android.webkit" flag { name: "update_service_ipc_wrapper" + is_exported: true namespace: "webview" description: "New API: proper wrapper for IWebViewUpdateService" bug: "319292658" diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 254f4f77c100..7fbec67ec4e9 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -18,6 +18,7 @@ flag { flag { name: "density_390_api" + is_exported: true namespace: "large_screen_experiences_app_compat" description: "Whether the API DisplayMetrics.DENSITY_390 is available" bug: "297550533" @@ -26,6 +27,7 @@ flag { flag { name: "app_compat_properties_api" + is_exported: true namespace: "large_screen_experiences_app_compat" description: "Whether app compat property APIs are public. Which includes: /n" "WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE,/n" diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig index ea9da96496c7..dea9497dd624 100644 --- a/core/java/android/window/flags/wallpaper_manager.aconfig +++ b/core/java/android/window/flags/wallpaper_manager.aconfig @@ -2,6 +2,7 @@ package: "com.android.window.flags" flag { name: "always_update_wallpaper_permission" + is_exported: true namespace: "wear_frameworks" description: "Allow out of focus process to update wallpaper complications" bug: "271132915" diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 3f483418c6b3..00b600c662f5 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -45,6 +45,7 @@ flag { flag { namespace: "window_surfaces" name: "trusted_presentation_listener_for_window" + is_exported: true description: "Enable trustedPresentationListener on windows public API" is_fixed_read_only: true bug: "278027319" @@ -53,6 +54,7 @@ flag { flag { namespace: "window_surfaces" name: "sdk_desired_present_time" + is_exported: true description: "Feature flag for the new SDK API to set desired present time" is_fixed_read_only: true bug: "295038072" @@ -61,6 +63,7 @@ flag { flag { namespace: "window_surfaces" name: "surface_control_input_receiver" + is_exported: true description: "Enable public API to register an InputReceiver for a SurfaceControl" is_fixed_read_only: true bug: "278757236" @@ -69,6 +72,7 @@ flag { flag { namespace: "window_surfaces" name: "screen_recording_callbacks" + is_exported: true description: "Enable screen recording callbacks public API" is_fixed_read_only: true bug: "304574518" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 65bf24179bea..247f28c887f5 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -16,6 +16,7 @@ flag { flag { name: "enforce_edge_to_edge" + is_exported: true namespace: "windowing_frontend" description: "Make app go edge-to-edge when targeting SDK level 35 or greater" bug: "309578419" @@ -88,6 +89,7 @@ flag { flag { name: "supports_multi_instance_system_ui" + is_exported: true namespace: "multitasking" description: "Feature flag to enable a multi-instance system ui component property." bug: "262864589" @@ -96,6 +98,7 @@ flag { flag { name: "delegate_unhandled_drags" + is_exported: true namespace: "multitasking" description: "Enables delegating unhandled drags to SystemUI" bug: "320797628" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 82e613e18d41..4b3d8e809eca 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -43,6 +43,7 @@ flag { flag { namespace: "windowing_sdk" name: "untrusted_embedding_any_app_permission" + is_exported: true description: "Feature flag to enable the permission to embed any app in untrusted mode." bug: "293647332" is_fixed_read_only: true @@ -59,6 +60,7 @@ flag { flag { namespace: "windowing_sdk" name: "untrusted_embedding_state_sharing" + is_exported: true description: "Feature flag to enable state sharing in untrusted embedding when apps opt in." bug: "293647332" is_fixed_read_only: true @@ -74,6 +76,7 @@ flag { flag { namespace: "windowing_sdk" name: "cover_display_opt_in" + is_exported: true description: "Properties to allow apps and activities to opt-in to cover display rendering" bug: "312530526" is_fixed_read_only: true diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig index 6c81a608241c..1e41b4d9ed1b 100644 --- a/graphics/java/android/framework_graphics.aconfig +++ b/graphics/java/android/framework_graphics.aconfig @@ -2,6 +2,7 @@ package: "com.android.graphics.flags" flag { name: "exact_compute_bounds" + is_exported: true namespace: "core_graphics" description: "Add a function without unused exact param for computeBounds." bug: "304478551" @@ -9,6 +10,7 @@ flag { flag { name: "yuv_image_compress_to_ultra_hdr" + is_exported: true namespace: "core_graphics" description: "Feature flag for YUV image compress to Ultra HDR." bug: "308978825" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt index 4c34971c4fb1..9e8dfb5f0c6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/MultiInstanceHelper.kt @@ -21,11 +21,9 @@ import android.content.Context import android.content.pm.LauncherApps import android.content.pm.PackageManager import android.os.UserHandle -import android.view.WindowManager import android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.R -import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL import com.android.wm.shell.util.KtProtoLog import java.util.Arrays @@ -37,7 +35,8 @@ class MultiInstanceHelper @JvmOverloads constructor( private val context: Context, private val packageManager: PackageManager, private val staticAppsSupportingMultiInstance: Array<String> = context.resources - .getStringArray(R.array.config_appsSupportMultiInstancesSplit)) { + .getStringArray(R.array.config_appsSupportMultiInstancesSplit), + private val supportsMultiInstanceProperty: Boolean) { /** * Returns whether a specific component desires to be launched in multiple instances. @@ -59,6 +58,11 @@ class MultiInstanceHelper @JvmOverloads constructor( } } + if (!supportsMultiInstanceProperty) { + // If not checking the multi-instance properties, then return early + return false; + } + // Check the activity property first try { val activityProp = packageManager.getProperty( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 8d489e106ae1..512211460753 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -29,6 +29,7 @@ import android.window.SystemPerformanceHinter; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; +import com.android.window.flags.Flags; import com.android.wm.shell.ProtoLogController; import com.android.wm.shell.R; import com.android.wm.shell.RootDisplayAreaOrganizer; @@ -326,7 +327,8 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static MultiInstanceHelper provideMultiInstanceHelper(Context context) { - return new MultiInstanceHelper(context, context.getPackageManager()); + return new MultiInstanceHelper(context, context.getPackageManager(), + Flags.supportsMultiInstanceSystemUi()); } // diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt index 2f5fe11634a4..bec91e910cf7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/MultiInstanceHelperTest.kt @@ -32,9 +32,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq +import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @@ -77,7 +80,7 @@ class MultiInstanceHelperTest : ShellTestCase() { @Test fun supportsMultiInstanceSplit_inStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) assertEquals(true, helper.supportsMultiInstanceSplit(component)) } @@ -85,7 +88,7 @@ class MultiInstanceHelperTest : ShellTestCase() { @Test fun supportsMultiInstanceSplit_notInStaticAllowList() { val allowList = arrayOf(TEST_PACKAGE) - val helper = MultiInstanceHelper(mContext, context.packageManager, allowList) + val helper = MultiInstanceHelper(mContext, context.packageManager, allowList, true) val component = ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY) assertEquals(false, helper.supportsMultiInstanceSplit(component)) } @@ -104,7 +107,7 @@ class MultiInstanceHelperTest : ShellTestCase() { eq(component.packageName))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray()) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) // Expect activity property to override application property assertEquals(true, helper.supportsMultiInstanceSplit(component)) } @@ -123,7 +126,7 @@ class MultiInstanceHelperTest : ShellTestCase() { eq(component.packageName))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray()) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) // Expect activity property to override application property assertEquals(false, helper.supportsMultiInstanceSplit(component)) } @@ -141,7 +144,7 @@ class MultiInstanceHelperTest : ShellTestCase() { eq(component.packageName))) .thenReturn(appProp) - val helper = MultiInstanceHelper(mContext, pm, emptyArray()) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) // Expect fall through to app property assertEquals(true, helper.supportsMultiInstanceSplit(component)) } @@ -158,10 +161,30 @@ class MultiInstanceHelperTest : ShellTestCase() { eq(component.packageName))) .thenThrow(PackageManager.NameNotFoundException()) - val helper = MultiInstanceHelper(mContext, pm, emptyArray()) + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), true) assertEquals(false, helper.supportsMultiInstanceSplit(component)) } + @Test + @Throws(PackageManager.NameNotFoundException::class) + fun checkNoMultiInstancePropertyFlag_ignoreProperty() { + val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY) + val pm = mock<PackageManager>() + val activityProp = PackageManager.Property("", true, "", "") + whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component))) + .thenReturn(activityProp) + val appProp = PackageManager.Property("", true, "", "") + whenever(pm.getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), + eq(component.packageName))) + .thenReturn(appProp) + + val helper = MultiInstanceHelper(mContext, pm, emptyArray(), false) + // Expect we only check the static list and not the property + assertEquals(false, helper.supportsMultiInstanceSplit(component)) + verify(pm, never()).getProperty(any(), any<ComponentName>()) + } + companion object { val TEST_PACKAGE = "com.android.wm.shell.common" val TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.common.fake"; diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index 76a0a6499d33..659bcdc6852d 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -2,6 +2,7 @@ package: "com.android.graphics.hwui.flags" flag { name: "clip_shader" + is_exported: true namespace: "core_graphics" description: "API for canvas shader clipping operations" bug: "280116960" @@ -9,6 +10,7 @@ flag { flag { name: "matrix_44" + is_exported: true namespace: "core_graphics" description: "API for 4x4 matrix and related canvas functions" bug: "280116960" @@ -16,6 +18,7 @@ flag { flag { name: "limited_hdr" + is_exported: true namespace: "core_graphics" description: "API to enable apps to restrict the amount of HDR headroom that is used" bug: "234181960" @@ -44,6 +47,7 @@ flag { flag { name: "gainmap_animations" + is_exported: true namespace: "core_graphics" description: "APIs to help enable animations involving gainmaps" bug: "296482289" @@ -51,6 +55,7 @@ flag { flag { name: "gainmap_constructor_with_metadata" + is_exported: true namespace: "core_graphics" description: "APIs to create a new gainmap with a bitmap for metadata." bug: "304478551" @@ -65,6 +70,7 @@ flag { flag { name: "requested_formats_v" + is_exported: true namespace: "core_graphics" description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK" bug: "292545615" diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index f33bcb7f9643..ce689aca51bf 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -9,6 +9,7 @@ flag { flag { name: "location_bypass" + is_exported: true namespace: "location" description: "Enable location bypass appops behavior" bug: "329151785" diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig index c3997e94622d..5bf1b4ef96ff 100644 --- a/media/java/android/media/flags/editing.aconfig +++ b/media/java/android/media/flags/editing.aconfig @@ -2,6 +2,7 @@ package: "com.android.media.editing.flags" flag { name: "add_media_metrics_editing" + is_exported: true namespace: "media_solutions" description: "Add media metrics for transcoding/editing events." bug: "297487694" diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index bf3942559b8a..40929f79eeb6 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -2,6 +2,7 @@ package: "com.android.media.flags" flag { name: "enable_rlp_callbacks_in_media_router2" + is_exported: true namespace: "media_solutions" description: "Make RouteListingPreference getter and callbacks public in MediaRouter2." bug: "281067101" @@ -16,6 +17,7 @@ flag { flag { name: "enable_audio_policies_device_and_bluetooth_controller" + is_exported: true namespace: "media_solutions" description: "Use Audio Policies implementation for device and Bluetooth route controllers." bug: "280576228" @@ -44,6 +46,7 @@ flag { flag { name: "enable_new_media_route_2_info_types" + is_exported: true namespace: "media_solutions" description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols." bug: "301713440" @@ -51,6 +54,7 @@ flag { flag { name: "enable_privileged_routing_for_media_routing_control" + is_exported: true namespace: "media_solutions" description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders." bug: "305919655" @@ -58,6 +62,7 @@ flag { flag { name: "enable_cross_user_routing_in_media_router2" + is_exported: true namespace: "media_solutions" description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users." bug: "288580225" @@ -72,6 +77,7 @@ flag { flag { name: "enable_built_in_speaker_route_suitability_statuses" + is_exported: true namespace: "media_solutions" description: "Make MediaRoute2Info provide information about routes suitability for transfer." bug: "279555229" @@ -79,6 +85,7 @@ flag { flag { name: "enable_notifying_activity_manager_with_media_session_status_change" + is_exported: true namespace: "media_solutions" description: "Notify ActivityManager with the changes in playback state of the media session." bug: "295518668" @@ -86,6 +93,7 @@ flag { flag { name: "enable_get_transferable_routes" + is_exported: true namespace: "media_solutions" description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API." bug: "323154573" @@ -100,6 +108,7 @@ flag { flag { name: "enable_screen_off_scanning" + is_exported: true namespace: "media_solutions" description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off." bug: "281072508" diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig index f1107059111c..1731e5e4335c 100644 --- a/media/java/android/media/tv/flags/media_tv.aconfig +++ b/media/java/android/media/tv/flags/media_tv.aconfig @@ -2,6 +2,7 @@ package: "android.media.tv.flags" flag { name: "broadcast_visibility_types" + is_exported: true namespace: "media_tv" description: "Constants for standardizing broadcast visibility types." bug: "222402395" @@ -9,6 +10,7 @@ flag { flag { name: "enable_ad_service_fw" + is_exported: true namespace: "media_tv" description: "Enable the TV client-side AD framework." bug: "303506816" @@ -16,6 +18,7 @@ flag { flag { name: "tiaf_v_apis" + is_exported: true namespace: "media_tv" description: "TIAF V3.0 APIs for Android V" bug: "303323657" diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index ba084c0901c4..6d4a17c27da0 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -63,6 +63,7 @@ flag { flag { name: "enable_nfc_charging" + is_exported: true namespace: "nfc" description: "Flag for NFC charging changes" bug: "292143899" diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig index 572a66922ea3..8627eac7beed 100644 --- a/packages/CrashRecovery/aconfig/flags.aconfig +++ b/packages/CrashRecovery/aconfig/flags.aconfig @@ -10,6 +10,7 @@ flag { flag { name: "enable_crashrecovery" + is_exported: true namespace: "crashrecovery" description: "Enables various dependencies of crashrecovery module" bug: "289203818" diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm index 071f9f436e04..854c2fdc71ce 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm @@ -23,8 +23,8 @@ map key 43 POUND ### ROW 1 key GRAVE { - label: '`' - base: '`' + label: '\u0300' + base: '\u0300' shift: '\u00AC' ralt: '\u00A6' } @@ -39,6 +39,7 @@ key 2 { label: '2' base: '2' shift: '"' + ralt: '\u0308' } key 3 { @@ -64,6 +65,7 @@ key 6 { label: '6' base: '6' shift: '^' + ralt: '\u0302' } key 7 { @@ -202,6 +204,7 @@ key RIGHT_BRACKET { label: ']' base: ']' shift: '}' + shift+ralt: '|' } ### ROW 3 @@ -282,14 +285,16 @@ key APOSTROPHE { label: '\'' base: '\'' shift: '@' + ralt: '\u0301' + shift+ralt: '`' } key POUND { label: '#' base: '#' shift: '~' - ralt: '\\' - shift+ralt: '|' + ralt: '\u0303' + shift+ralt: '\\' } ### ROW 4 diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index 416b36981a4c..baccda7e3cc4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -163,6 +163,16 @@ public interface BluetoothCallback { default void onAclConnectionStateChanged( @NonNull CachedBluetoothDevice cachedDevice, int state) {} + /** + * Called when the Auto-on state is changed for any user. Listens to intent + * {@link android.bluetooth.BluetoothAdapter#ACTION_AUTO_ON_STATE_CHANGED } + * + * @param state the Auto-on state, the possible values are: + * {@link android.bluetooth.BluetoothAdapter#AUTO_ON_STATE_ENABLED}, + * {@link android.bluetooth.BluetoothAdapter#AUTO_ON_STATE_DISABLED} + */ + default void onAutoOnStateChanged(int state) {} + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "STATE_" }, value = { STATE_DISCONNECTED, diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 647fcb9f67fa..0996d52b0e30 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -133,6 +133,8 @@ public class BluetoothEventManager { addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler()); addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler()); + addHandler(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED, new AutoOnStateChangedHandler()); + registerAdapterIntentReceiver(); } @@ -552,4 +554,21 @@ public class BluetoothEventManager { dispatchAudioModeChanged(); } } + + private class AutoOnStateChangedHandler implements Handler { + + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "AutoOnStateChangedHandler() action is null"); + return; + } + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_AUTO_ON_STATE, + BluetoothAdapter.ERROR); + for (BluetoothCallback callback : mCallbacks) { + callback.onAutoOnStateChanged(state); + } + } + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 13635c3a8256..48bbf4ea6a65 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -489,4 +490,17 @@ public class BluetoothEventManagerTest { verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME), eq(R.string.bluetooth_pairing_pin_error_message)); } + + /** + * Intent ACTION_AUTO_ON_STATE_CHANGED should dispatch to callback. + */ + @Test + public void intentWithExtraState_autoOnStateChangedShouldDispatchToRegisterCallback() { + mBluetoothEventManager.registerCallback(mBluetoothCallback); + mIntent = new Intent(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED); + + mContext.sendBroadcast(mIntent); + + verify(mBluetoothCallback).onAutoOnStateChanged(anyInt()); + } } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a155dc4d7639..f057acc71b98 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -25,6 +25,16 @@ flag { } flag { + name: "notification_minimalism_prototype" + namespace: "systemui" + description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal." + bug: "330387368" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "notification_view_flipper_pausing" namespace: "systemui" description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed." diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 1da6c1ee6638..0f3d3dc2847f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -484,6 +484,7 @@ private fun FoldAware( onChangeScene = {}, transitions = SceneTransitions, modifier = modifier, + enableInterruptions = false, ) { scene(SceneKeys.ContiguousSceneKey) { FoldableScene( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index d0c498475d0b..a1d8c29c2a39 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -71,6 +71,7 @@ fun CommunalContainer( currentScene, onChangeScene = { viewModel.onSceneChanged(it) }, transitions = sceneTransitions, + enableInterruptions = false, ) val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index bc4e55505579..1178cc843d60 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -74,6 +74,7 @@ constructor( transitions = transitions { sceneKeyByBlueprintId.values.forEach { sceneKey -> to(sceneKey) } }, modifier = modifier, + enableInterruptions = false, ) { sceneKeyByBlueprint.entries.forEach { (blueprint, sceneKey) -> scene(sceneKey) { with(blueprint) { Content(Modifier.fillMaxSize()) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt index d9ed4976bb34..a12f0990b581 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ClockTransition.kt @@ -80,5 +80,14 @@ object ClockScenes { object ClockElementKeys { val largeClockElementKey = ElementKey("large-clock") val smallClockElementKey = ElementKey("small-clock") + val weatherSmallClockElementKey = ElementKey("weather-small-clock") val smartspaceElementKey = ElementKey("smart-space") } + +object WeatherClockElementKeys { + val timeElementKey = ElementKey("weather-large-clock-time") + val dateElementKey = ElementKey("weather-large-clock-date") + val weatherIconElementKey = ElementKey("weather-large-clock-weather-icon") + val temperatureElementKey = ElementKey("weather-large-clock-temperature") + val dndAlarmElementKey = ElementKey("weather-large-clock-dnd-alarm") +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt index ee4e2d697833..fe774a0d6db2 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt @@ -23,6 +23,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout @@ -33,11 +35,14 @@ import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding +import com.android.keyguard.KeyguardClockSwitch.LARGE import com.android.systemui.Flags +import com.android.systemui.customization.R as customizationR import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor.Companion.WEATHER_CLOCK_BLUEPRINT_ID import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.LockscreenLongPress +import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection import com.android.systemui.keyguard.ui.composable.section.LockSection @@ -47,8 +52,8 @@ import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection import com.android.systemui.keyguard.ui.composable.section.StatusBarSection import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel -import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import dagger.Binds @@ -71,6 +76,7 @@ constructor( private val settingsMenuSection: SettingsMenuSection, private val clockInteractor: KeyguardClockInteractor, private val mediaCarouselSection: MediaCarouselSection, + private val clockViewModel: KeyguardClockViewModel, ) : ComposableLockscreenSceneBlueprint { override val id: String = WEATHER_CLOCK_BLUEPRINT_ID @@ -79,7 +85,7 @@ constructor( val isUdfpsVisible = viewModel.isUdfpsVisible val burnIn = rememberBurnIn(clockInteractor) val resources = LocalContext.current.resources - + val currentClockState = clockViewModel.currentClock.collectAsState() LockscreenLongPress( viewModel = viewModel.longPress, modifier = modifier, @@ -91,7 +97,34 @@ constructor( modifier = Modifier.fillMaxWidth(), ) { with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) } - // TODO: Add weather clock for small and large clock + val currentClock = currentClockState.value + val clockSize by clockViewModel.clockSize.collectAsState() + with(weatherClockSection) { + if (currentClock == null) { + return@with + } + + if (clockSize == LARGE) { + Time( + clock = currentClock, + modifier = + Modifier.padding( + start = + dimensionResource( + customizationR.dimen.clock_padding_start + ) + ) + ) + } else { + SmallClock( + burnInParams = burnIn.parameters, + modifier = + Modifier.align(Alignment.Start) + .onTopPlacementChanged(burnIn.onSmallClockTopChanged), + clock = currentClock + ) + } + } with(smartSpaceSection) { SmartSpace( burnInParams = burnIn.parameters, @@ -119,6 +152,12 @@ constructor( ) } } + with(weatherClockSection) { + if (currentClock == null || clockSize != LARGE) { + return@with + } + LargeClockSectionBelowSmartspace(clock = currentClock) + } if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { @@ -234,6 +273,7 @@ constructor( private val largeScreenHeaderHelper: LargeScreenHeaderHelper, private val weatherClockSection: WeatherClockSection, private val mediaCarouselSection: MediaCarouselSection, + private val clockViewModel: KeyguardClockViewModel, ) : ComposableLockscreenSceneBlueprint { override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID @@ -242,7 +282,7 @@ constructor( val isUdfpsVisible = viewModel.isUdfpsVisible val burnIn = rememberBurnIn(clockInteractor) val resources = LocalContext.current.resources - + val currentClockState = clockViewModel.currentClock.collectAsState() LockscreenLongPress( viewModel = viewModel.longPress, modifier = modifier, @@ -257,11 +297,42 @@ constructor( Row( modifier = Modifier.fillMaxSize(), ) { - // TODO: Add weather clock for small and large clock Column( modifier = Modifier.fillMaxHeight().weight(weight = 1f), horizontalAlignment = Alignment.CenterHorizontally, ) { + val currentClock = currentClockState.value + val clockSize by clockViewModel.clockSize.collectAsState() + with(weatherClockSection) { + if (currentClock == null) { + return@with + } + + if (clockSize == LARGE) { + Time( + clock = currentClock, + modifier = + Modifier.align(Alignment.Start) + .padding( + start = + dimensionResource( + customizationR.dimen + .clock_padding_start + ) + ) + ) + } else { + SmallClock( + burnInParams = burnIn.parameters, + modifier = + Modifier.align(Alignment.Start) + .onTopPlacementChanged( + burnIn.onSmallClockTopChanged + ), + clock = currentClock, + ) + } + } with(smartSpaceSection) { SmartSpace( burnInParams = burnIn.parameters, @@ -284,6 +355,14 @@ constructor( } with(mediaCarouselSection) { MediaCarousel() } + + with(weatherClockSection) { + if (currentClock == null || clockSize != LARGE) { + return@with + } + + LargeClockSectionBelowSmartspace(currentClock) + } } with(notificationSection) { val splitShadeTopMargin: Dp = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 1d86b15dbf4f..2781f39fc479 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -20,6 +20,7 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -32,6 +33,7 @@ import androidx.core.view.contains import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.customization.R as customizationR +import com.android.systemui.customization.R import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey import com.android.systemui.keyguard.ui.composable.modifier.burnInAware @@ -58,19 +60,21 @@ constructor( if (currentClock?.smallClock?.view == null) { return } - val context = LocalContext.current MovableElement(key = smallClockElementKey, modifier = modifier) { content { AndroidView( factory = { context -> FrameLayout(context).apply { - addClockView(checkNotNull(currentClock).smallClock.view) + ensureClockViewExists(checkNotNull(currentClock).smallClock.view) } }, - update = { it.addClockView(checkNotNull(currentClock).smallClock.view) }, + update = { + it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) + }, modifier = - Modifier.padding( + Modifier.height(dimensionResource(R.dimen.small_clock_height)) + .padding( horizontal = dimensionResource(customizationR.dimen.clock_padding_start) ) @@ -91,23 +95,24 @@ constructor( if (currentClock?.largeClock?.view == null) { return } - MovableElement(key = largeClockElementKey, modifier = modifier) { content { AndroidView( factory = { context -> FrameLayout(context).apply { - addClockView(checkNotNull(currentClock).largeClock.view) + ensureClockViewExists(checkNotNull(currentClock).largeClock.view) } }, - update = { it.addClockView(checkNotNull(currentClock).largeClock.view) }, + update = { + it.ensureClockViewExists(checkNotNull(currentClock).largeClock.view) + }, modifier = Modifier.fillMaxSize() ) } } } - private fun FrameLayout.addClockView(clockView: View) { + private fun FrameLayout.ensureClockViewExists(clockView: View) { if (contains(clockView)) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 763584182c97..d72d5cad31b4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -92,6 +92,7 @@ constructor( currentScene = currentScene, onChangeScene = {}, transitions = ClockTransition.defaultClockTransitions, + enableInterruptions = false, ) { scene(ClockScenes.splitShadeLargeClockScene) { Row( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt index 2e7bc2a28c65..d3584539b3fa 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/WeatherClockSection.kt @@ -16,45 +16,177 @@ package com.android.systemui.keyguard.ui.composable.section +import android.view.ViewGroup +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.viewinterop.AndroidView +import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope +import com.android.compose.modifiers.padding +import com.android.systemui.customization.R +import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.weatherSmallClockElementKey +import com.android.systemui.keyguard.ui.composable.blueprint.WeatherClockElementKeys +import com.android.systemui.keyguard.ui.composable.modifier.burnInAware +import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.plugins.clocks.ClockController import javax.inject.Inject /** Provides small clock and large clock composables for the weather clock layout. */ -class WeatherClockSection @Inject constructor() { +class WeatherClockSection +@Inject +constructor( + private val viewModel: KeyguardClockViewModel, + private val aodBurnInViewModel: AodBurnInViewModel, +) { @Composable fun SceneScope.Time( + clock: ClockController, modifier: Modifier = Modifier, ) { - // TODO: compose view + WeatherElement( + weatherClockElementViewId = R.id.weather_clock_time, + clock = clock, + elementKey = WeatherClockElementKeys.timeElementKey, + modifier = modifier.wrapContentSize(), + ) } @Composable - fun SceneScope.Date( + private fun SceneScope.Date( + clock: ClockController, modifier: Modifier = Modifier, ) { - // TODO: compose view + WeatherElement( + weatherClockElementViewId = R.id.weather_clock_date, + clock = clock, + elementKey = WeatherClockElementKeys.dateElementKey, + modifier = modifier, + ) } @Composable - fun SceneScope.Weather( + private fun SceneScope.Weather( + clock: ClockController, modifier: Modifier = Modifier, ) { - // TODO: compose view + WeatherElement( + weatherClockElementViewId = R.id.weather_clock_weather_icon, + clock = clock, + elementKey = WeatherClockElementKeys.weatherIconElementKey, + modifier = modifier.wrapContentSize(), + ) } @Composable - fun SceneScope.DndAlarmStatus( + private fun SceneScope.DndAlarmStatus( + clock: ClockController, modifier: Modifier = Modifier, ) { - // TODO: compose view + WeatherElement( + weatherClockElementViewId = R.id.weather_clock_alarm_dnd, + clock = clock, + elementKey = WeatherClockElementKeys.dndAlarmElementKey, + modifier = modifier.wrapContentSize(), + ) } @Composable - fun SceneScope.Temperature( + private fun SceneScope.Temperature( + clock: ClockController, modifier: Modifier = Modifier, ) { - // TODO: compose view + WeatherElement( + weatherClockElementViewId = R.id.weather_clock_temperature, + clock = clock, + elementKey = WeatherClockElementKeys.temperatureElementKey, + modifier = modifier.wrapContentSize(), + ) + } + + @Composable + private fun SceneScope.WeatherElement( + weatherClockElementViewId: Int, + clock: ClockController, + elementKey: ElementKey, + modifier: Modifier + ) { + MovableElement(key = elementKey, modifier) { + content { + AndroidView( + factory = { + val view = + clock.largeClock.layout.views.first { + it.id == weatherClockElementViewId + } + (view.parent as? ViewGroup)?.removeView(view) + view + }, + update = {}, + modifier = modifier + ) + } + } + } + + @Composable + fun SceneScope.LargeClockSectionBelowSmartspace( + clock: ClockController, + ) { + Row( + modifier = + Modifier.height(IntrinsicSize.Max) + .padding(horizontal = dimensionResource(R.dimen.clock_padding_start)) + ) { + Date(clock = clock, modifier = Modifier.wrapContentSize()) + Box(modifier = Modifier.fillMaxSize()) { + Weather(clock = clock, modifier = Modifier.align(Alignment.TopStart)) + Temperature(clock = clock, modifier = Modifier.align(Alignment.BottomEnd)) + DndAlarmStatus(clock = clock, modifier = Modifier.align(Alignment.TopEnd)) + } + } + } + + @Composable + fun SceneScope.SmallClock( + burnInParams: BurnInParameters, + modifier: Modifier = Modifier, + clock: ClockController, + ) { + val localContext = LocalContext.current + MovableElement(key = weatherSmallClockElementKey, modifier) { + content { + AndroidView( + factory = { + val view = clock.smallClock.view + if (view.parent != null) { + (view.parent as? ViewGroup)?.removeView(view) + } + view + }, + modifier = + modifier + .height(dimensionResource(R.dimen.small_clock_height)) + .padding(start = dimensionResource(R.dimen.clock_padding_start)) + .padding(top = { viewModel.getSmallClockTopMargin(localContext) }) + .burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ), + update = {}, + ) + } + } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 0fdaabe75306..fe6701cc8d89 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -79,6 +79,7 @@ fun SceneContainer( initialScene = currentSceneKey, canChangeScene = { toScene -> viewModel.canChangeScene(toScene) }, transitions = SceneContainerTransitions, + enableInterruptions = false, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 6cff30cf0369..da07f6d12a67 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -138,8 +138,9 @@ private fun CoroutineScope.animate( // that will actually animate it. layoutState.startTransition(transition, transitionKey) - // The transformation now contains the spec that we should use to instantiate the Animatable. - val animationSpec = layoutState.transformationSpec.progressSpec + // The transition now contains the transformation spec that we should use to instantiate the + // Animatable. + val animationSpec = transition.transformationSpec.progressSpec val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 82083f99ba3e..1b0627576af7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -18,10 +18,8 @@ package com.android.compose.animation.scene -import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D -import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf @@ -145,16 +143,6 @@ internal class DraggableHandlerImpl( } val transitionState = layoutImpl.state.transitionState - if (transitionState is TransitionState.Transition) { - // TODO(b/290184746): Better handle interruptions here if state != idle. - Log.w( - TAG, - "start from TransitionState.Transition is not fully supported: from" + - " ${transitionState.fromScene} to ${transitionState.toScene} " + - "(progress ${transitionState.progress})" - ) - } - val fromScene = layoutImpl.scene(transitionState.currentScene) val swipes = computeSwipes(fromScene, startedPosition, pointersDown) val result = @@ -269,19 +257,6 @@ private class DragControllerImpl( fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) { if (isDrivingTransition || force) { layoutState.startTransition(newTransition, newTransition.key) - - // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be - // called right after layoutState.startTransition() is called, because it computes the - // current layoutState.transformationSpec(). - val transformationSpec = layoutState.transformationSpec - newTransition.transformationSpec = transformationSpec - newTransition.swipeSpec = - transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec - } else { - // We were not driving the transition and we don't force the update, so the specs won't - // be used and it doesn't matter which ones we set here. - newTransition.transformationSpec = TransformationSpec.Empty - newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec } swipeTransition = newTransition @@ -616,18 +591,6 @@ private class SwipeTransition( override val isUserInputOngoing: Boolean get() = offsetAnimation == null - /** - * The [TransformationSpecImpl] associated to this transition. - * - * Note: This is lateinit because this [SwipeTransition] is needed by - * [BaseSceneTransitionLayoutState] to compute the [TransitionSpec], and it will be set right - * after [BaseSceneTransitionLayoutState.startTransition] is called with this transition. - */ - lateinit var transformationSpec: TransformationSpecImpl - - /** The spec to use when animating this transition to either [fromScene] or [toScene]. */ - lateinit var swipeSpec: SpringSpec<Float> - override val overscrollScope: OverscrollScope = object : OverscrollScope { override val absoluteDistance: Float @@ -701,6 +664,9 @@ private class SwipeTransition( coroutineScope .launch { try { + val swipeSpec = + transformationSpec.swipeSpec + ?: layoutState.transitions.defaultSwipeSpec animatable.animateTo( targetValue = targetOffset, animationSpec = swipeSpec, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 15712b5c7206..69f1d456b2fb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -203,7 +203,7 @@ internal class ElementNode( measurable: Measurable, constraints: Constraints, ): MeasureResult { - val overscrollScene = layoutImpl.state.currentOverscrollSpec?.scene + val overscrollScene = layoutImpl.state.currentTransition?.currentOverscrollSpec?.scene if (overscrollScene != null && overscrollScene != scene.key) { // There is an overscroll in progress on another scene // By measuring composable elements, Compose can cache relevant information. @@ -269,13 +269,12 @@ private fun shouldDrawElement( transition == null || transition.fromScene !in element.sceneStates || transition.toScene !in element.sceneStates || - layoutImpl.state.currentOverscrollSpec?.scene == scene.key + transition.currentOverscrollSpec?.scene == scene.key ) { return true } - val sharedTransformation = - sharedElementTransformation(layoutImpl.state, transition, element.key) + val sharedTransformation = sharedElementTransformation(transition, element.key) if (sharedTransformation?.enabled == false) { return true } @@ -305,23 +304,21 @@ internal fun shouldDrawOrComposeSharedElement( fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, ) == scene - return chosenByPicker || layoutImpl.state.currentOverscrollSpec?.scene == scene + return chosenByPicker || transition.currentOverscrollSpec?.scene == scene } private fun isSharedElementEnabled( - layoutState: BaseSceneTransitionLayoutState, transition: TransitionState.Transition, element: ElementKey, ): Boolean { - return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true + return sharedElementTransformation(transition, element)?.enabled ?: true } internal fun sharedElementTransformation( - layoutState: BaseSceneTransitionLayoutState, transition: TransitionState.Transition, element: ElementKey, ): SharedElementTransformation? { - val transformationSpec = layoutState.transformationSpec + val transformationSpec = transition.transformationSpec val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared @@ -360,11 +357,11 @@ private fun isElementOpaque( } val isSharedElement = fromState != null && toState != null - if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { + if (isSharedElement && isSharedElementEnabled(transition, element.key)) { return true } - return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null + return transition.transformationSpec.transformations(element.key, scene.key).alpha == null } /** @@ -559,7 +556,7 @@ private inline fun <T> computeValue( } if (transition is TransitionState.HasOverscrollProperties) { - val overscroll = layoutImpl.state.currentOverscrollSpec + val overscroll = transition.currentOverscrollSpec if (overscroll?.scene == scene.key) { val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key) val propertySpec = transformation(elementSpec) ?: return currentValue() @@ -597,7 +594,7 @@ private inline fun <T> computeValue( // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared // elements follow the finger direction. val isSharedElement = fromState != null && toState != null - if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) { + if (isSharedElement && isSharedElementEnabled(transition, element.key)) { val start = sceneValue(fromState!!) val end = sceneValue(toState!!) @@ -607,7 +604,7 @@ private inline fun <T> computeValue( } val transformation = - transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key)) + transformation(transition.transformationSpec.transformations(element.key, scene.key)) // If there is no transformation explicitly associated to this element value, let's use // the value given by the system (like the current position and size given by the layout // pass). diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index ebc90990275d..c7c874c1185d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -96,9 +96,17 @@ fun SceneTransitionLayout( modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f, + enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, scenes: SceneTransitionLayoutScope.() -> Unit, ) { - val state = updateSceneTransitionLayoutState(currentScene, onChangeScene, transitions) + val state = + updateSceneTransitionLayoutState( + currentScene, + onChangeScene, + transitions, + enableInterruptions = enableInterruptions, + ) + SceneTransitionLayout( state, modifier, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 617a8ea0b6cd..f13c016e9d68 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -16,15 +16,16 @@ package com.android.compose.animation.scene +import android.util.Log +import androidx.annotation.VisibleForTesting import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastForEach import com.android.compose.animation.scene.transition.link.LinkedTransition @@ -50,10 +51,21 @@ sealed interface SceneTransitionLayoutState { */ val transitionState: TransitionState - /** The current transition, or `null` if we are idle. */ + /** + * The current transition, or `null` if we are idle. + * + * Note: If you need to handle interruptions and multiple transitions running in parallel, use + * [currentTransitions] instead. + */ val currentTransition: TransitionState.Transition? get() = transitionState as? TransitionState.Transition + /** + * The list of [TransitionState.Transition] currently running. This will be the empty list if we + * are idle. + */ + val currentTransitions: List<TransitionState.Transition> + /** The [SceneTransitions] used when animating this state. */ val transitions: SceneTransitions @@ -120,12 +132,14 @@ fun MutableSceneTransitionLayoutState( transitions: SceneTransitions = SceneTransitions.Empty, canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), + enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, ): MutableSceneTransitionLayoutState { return MutableSceneTransitionLayoutStateImpl( initialScene, transitions, canChangeScene, stateLinks, + enableInterruptions, ) } @@ -154,6 +168,7 @@ fun updateSceneTransitionLayoutState( transitions: SceneTransitions = SceneTransitions.Empty, canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), + enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, ): SceneTransitionLayoutState { return remember { HoistedSceneTransitionLayoutState( @@ -162,9 +177,19 @@ fun updateSceneTransitionLayoutState( onChangeScene, canChangeScene, stateLinks, + enableInterruptions, + ) + } + .apply { + update( + currentScene, + onChangeScene, + canChangeScene, + transitions, + stateLinks, + enableInterruptions, ) } - .apply { update(currentScene, onChangeScene, canChangeScene, transitions, stateLinks) } } @Stable @@ -204,6 +229,30 @@ sealed interface TransitionState { /** Whether user input is currently driving the transition. */ abstract val isUserInputOngoing: Boolean + /** + * The current [TransformationSpecImpl] and [OverscrollSpecImpl] associated to this + * transition. + * + * Important: These will be set exactly once, when this transition is + * [started][BaseSceneTransitionLayoutState.startTransition]. + */ + internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty + private var fromOverscrollSpec: OverscrollSpecImpl? = null + private var toOverscrollSpec: OverscrollSpecImpl? = null + + /** The current [OverscrollSpecImpl], if this transition is currently overscrolling. */ + internal val currentOverscrollSpec: OverscrollSpecImpl? + get() { + if (this !is HasOverscrollProperties) return null + val progress = progress + val bouncingScene = bouncingScene + return when { + progress < 0f || bouncingScene == fromScene -> fromOverscrollSpec + progress > 1f || bouncingScene == toScene -> toOverscrollSpec + else -> null + } + } + init { check(fromScene != toScene) } @@ -232,6 +281,14 @@ sealed interface TransitionState { return isTransitioning(from = scene, to = other) || isTransitioning(from = other, to = scene) } + + internal fun updateOverscrollSpecs( + fromSpec: OverscrollSpecImpl?, + toSpec: OverscrollSpecImpl?, + ) { + fromOverscrollSpec = fromSpec + toOverscrollSpec = toSpec + } } interface HasOverscrollProperties { @@ -270,38 +327,41 @@ sealed interface TransitionState { internal abstract class BaseSceneTransitionLayoutState( initialScene: SceneKey, protected var stateLinks: List<StateLink>, -) : SceneTransitionLayoutState { - override var transitionState: TransitionState by - mutableStateOf(TransitionState.Idle(initialScene)) - protected set + // TODO(b/290930950): Remove this flag. + internal var enableInterruptions: Boolean, +) : SceneTransitionLayoutState { /** - * The current [transformationSpec] associated to [transitionState]. Accessing this value makes - * sense only if [transitionState] is a [TransitionState.Transition]. + * The current [TransitionState]. This list will either be: + * 1. A list with a single [TransitionState.Idle] element, when we are idle. + * 2. A list with one or more [TransitionState.Transition], when we are transitioning. */ - internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty + @VisibleForTesting + internal val transitionStates: MutableList<TransitionState> = + SnapshotStateList<TransitionState>().apply { add(TransitionState.Idle(initialScene)) } - private var fromOverscrollSpec: OverscrollSpecImpl? = null - private var toOverscrollSpec: OverscrollSpecImpl? = null + override val transitionState: TransitionState + get() = transitionStates.last() - /** - * @return the overscroll [OverscrollSpecImpl] if it is defined for the current - * [transitionState] and we are currently over scrolling. - */ - internal val currentOverscrollSpec: OverscrollSpecImpl? + private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() + + override val currentTransitions: List<TransitionState.Transition> get() { - val transition = currentTransition ?: return null - if (transition !is TransitionState.HasOverscrollProperties) return null - val progress = transition.progress - val bouncingScene = transition.bouncingScene - return when { - progress < 0f || bouncingScene == transition.fromScene -> fromOverscrollSpec - progress > 1f || bouncingScene == transition.toScene -> toOverscrollSpec - else -> null + if (transitionStates.last() is TransitionState.Idle) { + check(transitionStates.size == 1) + return emptyList() + } else { + @Suppress("UNCHECKED_CAST") + return transitionStates as List<TransitionState.Transition> } } - private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>() + /** + * The mapping of transitions that are finished, i.e. for which [finishTransition] was called, + * to their idle scene. + */ + @VisibleForTesting + internal val finishedTransitions = mutableMapOf<TransitionState.Transition, SceneKey>() /** Whether we can transition to the given [scene]. */ internal abstract fun canChangeScene(scene: SceneKey): Boolean @@ -324,7 +384,11 @@ internal abstract class BaseSceneTransitionLayoutState( return transition.isTransitioningBetween(scene, other) } - /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */ + /** + * Start a new [transition], instantly interrupting any ongoing transition if there was one. + * + * Important: you *must* call [finishTransition] once the transition is finished. + */ internal fun startTransition( transition: TransitionState.Transition, transitionKey: TransitionKey?, @@ -333,13 +397,81 @@ internal abstract class BaseSceneTransitionLayoutState( val fromScene = transition.fromScene val toScene = transition.toScene val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation - transformationSpec = + + // Update the transition specs. + transition.transformationSpec = transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec() - fromOverscrollSpec = orientation?.let { transitions.overscrollSpec(fromScene, it) } - toOverscrollSpec = orientation?.let { transitions.overscrollSpec(toScene, it) } + if (orientation != null) { + transition.updateOverscrollSpecs( + fromSpec = transitions.overscrollSpec(fromScene, orientation), + toSpec = transitions.overscrollSpec(toScene, orientation), + ) + } else { + transition.updateOverscrollSpecs(fromSpec = null, toSpec = null) + } + + // Handle transition links. cancelActiveTransitionLinks() setupTransitionLinks(transition) - transitionState = transition + + if (!enableInterruptions) { + // Set the current transition. + check(transitionStates.size == 1) + transitionStates[0] = transition + return + } + + when (val currentState = transitionStates.last()) { + is TransitionState.Idle -> { + // Replace [Idle] by [transition]. + check(transitionStates.size == 1) + transitionStates[0] = transition + } + is TransitionState.Transition -> { + // Force the current transition to finish to currentScene. + currentState.finish().invokeOnCompletion { + // Make sure [finishTransition] is called at the end of the transition. + finishTransition(currentState, currentState.currentScene) + } + + // Check that we don't have too many concurrent transitions. + if (transitionStates.size >= MAX_CONCURRENT_TRANSITIONS) { + Log.wtf( + TAG, + buildString { + appendLine("Potential leak detected in SceneTransitionLayoutState!") + appendLine( + " Some transition(s) never called STLState.finishTransition()." + ) + appendLine(" Transitions (size=${transitionStates.size}):") + transitionStates.fastForEach { state -> + val transition = state as TransitionState.Transition + val from = transition.fromScene + val to = transition.toScene + val indicator = + if (finishedTransitions.contains(transition)) "x" else " " + appendLine(" [$indicator] $from => $to ($transition)") + } + } + ) + + // Force finish all transitions. + while (currentTransitions.isNotEmpty()) { + val transition = transitionStates[0] as TransitionState.Transition + finishTransition(transition, transition.currentScene) + } + + // We finished all transitions, so we are now idle. We remove this state so that + // we end up only with the new transition after appending it. + check(transitionStates.size == 1) + check(transitionStates[0] is TransitionState.Idle) + transitionStates.clear() + } + + // Append the new transition. + transitionStates.add(transition) + } + } } private fun cancelActiveTransitionLinks() { @@ -379,13 +511,54 @@ internal abstract class BaseSceneTransitionLayoutState( * nothing if [transition] was interrupted since it was started. */ internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { - resolveActiveTransitionLinks(idleScene) - if (transitionState == transition) { - transitionState = TransitionState.Idle(idleScene) + val existingIdleScene = finishedTransitions[transition] + if (existingIdleScene != null) { + // This transition was already finished. + check(idleScene == existingIdleScene) { + "Transition $transition was finished multiple times with different " + + "idleScene ($existingIdleScene != $idleScene)" + } + return + } + + if (!transitionStates.contains(transition)) { + // This transition was already removed from transitionStates. + return + } + + check(transitionStates.fastAll { it is TransitionState.Transition }) + + // Mark this transition as finished and save the scene it is settling at. + finishedTransitions[transition] = idleScene + + // Finish all linked transitions. + finishActiveTransitionLinks(idleScene) + + // Keep a reference to the idle scene of the last removed transition, in case we remove all + // transitions and should settle to Idle. + var lastRemovedIdleScene: SceneKey? = null + + // Remove all first n finished transitions. + while (transitionStates.isNotEmpty()) { + val firstTransition = transitionStates[0] + if (!finishedTransitions.contains(firstTransition)) { + // Stop here. + break + } + + // Remove the transition from the list and from the set of finished transitions. + transitionStates.removeAt(0) + lastRemovedIdleScene = finishedTransitions.remove(firstTransition) + } + + // If all transitions are finished, we are idle. + if (transitionStates.isEmpty()) { + check(finishedTransitions.isEmpty()) + transitionStates.add(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) } } - private fun resolveActiveTransitionLinks(idleScene: SceneKey) { + private fun finishActiveTransitionLinks(idleScene: SceneKey) { val previousTransition = this.transitionState as? TransitionState.Transition ?: return for ((link, linkedTransition) in activeTransitionLinks) { if (previousTransition.fromScene == idleScene) { @@ -406,20 +579,39 @@ internal abstract class BaseSceneTransitionLayoutState( * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap * to the closest scene. * + * Important: Snapping to the closest scene will instantly finish *all* ongoing transitions, + * only the progress of the last transition will be checked. + * * @return true if snapped to the closest scene. */ internal fun snapToIdleIfClose(threshold: Float): Boolean { val transition = currentTransition ?: return false val progress = transition.progress + fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold + fun finishAllTransitions(lastTransitionIdleScene: SceneKey) { + // Force finish all transitions. + while (currentTransitions.isNotEmpty()) { + val transition = transitionStates[0] as TransitionState.Transition + val idleScene = + if (transitionStates.size == 1) { + lastTransitionIdleScene + } else { + transition.currentScene + } + + finishTransition(transition, idleScene) + } + } + return when { isProgressCloseTo(0f) -> { - finishTransition(transition, transition.fromScene) + finishAllTransitions(transition.fromScene) true } isProgressCloseTo(1f) -> { - finishTransition(transition, transition.toScene) + finishAllTransitions(transition.toScene) true } else -> false @@ -437,7 +629,8 @@ internal class HoistedSceneTransitionLayoutState( private var changeScene: (SceneKey) -> Unit, private var canChangeScene: (SceneKey) -> Boolean, stateLinks: List<StateLink> = emptyList(), -) : BaseSceneTransitionLayoutState(initialScene, stateLinks) { + enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, +) : BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) { private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED) override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene) @@ -451,12 +644,14 @@ internal class HoistedSceneTransitionLayoutState( canChangeScene: (SceneKey) -> Boolean, transitions: SceneTransitions, stateLinks: List<StateLink>, + enableInterruptions: Boolean, ) { SideEffect { this.changeScene = onChangeScene this.canChangeScene = canChangeScene this.transitions = transitions this.stateLinks = stateLinks + this.enableInterruptions = enableInterruptions targetSceneChannel.trySend(currentScene) } @@ -482,7 +677,10 @@ internal class MutableSceneTransitionLayoutStateImpl( override var transitions: SceneTransitions, private val canChangeScene: (SceneKey) -> Boolean = { true }, stateLinks: List<StateLink> = emptyList(), -) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) { + enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED, +) : + MutableSceneTransitionLayoutState, + BaseSceneTransitionLayoutState(initialScene, stateLinks, enableInterruptions) { override fun setTargetScene( targetScene: SceneKey, coroutineScope: CoroutineScope, @@ -501,3 +699,15 @@ internal class MutableSceneTransitionLayoutStateImpl( setTargetScene(scene, coroutineScope = this) } } + +private const val TAG = "SceneTransitionLayoutState" + +/** Whether support for interruptions in enabled by default. */ +internal const val DEFAULT_INTERRUPTIONS_ENABLED = true + +/** + * The max number of concurrent transitions. If the number of transitions goes past this number, + * this probably means that there is a leak and we will Log.wtf before clearing the list of + * transitions. + */ +private const val MAX_CONCURRENT_TRANSITIONS = 100 diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp index 59cc63aa5eef..af1389680bd2 100644 --- a/packages/SystemUI/compose/scene/tests/Android.bp +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -26,7 +26,6 @@ android_test { name: "PlatformComposeSceneTransitionLayoutTests", manifest: "AndroidManifest.xml", test_suites: ["device-tests"], - sdk_version: "current", certificate: "platform", srcs: [ diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 1e9a7e2bb667..2ed51eb9a280 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -984,14 +984,14 @@ class DraggableHandlerTest { val scene = layoutState.transitionState.currentScene // We should have overscroll spec for scene C assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull() - assertThat(layoutState.currentOverscrollSpec).isNull() + assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNull() val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f)) // We scrolled down, under scene C there is nothing, so we can use the overscroll spec - assertThat(layoutState.currentOverscrollSpec).isNotNull() - assertThat(layoutState.currentOverscrollSpec?.scene).isEqualTo(SceneC) + assertThat(layoutState.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(layoutState.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneC) val transition = layoutState.currentTransition assertThat(transition).isNotNull() assertThat(transition!!.progress).isEqualTo(-0.1f) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 597da9e82a1f..2453e251b5a4 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -595,7 +595,7 @@ class ElementTest { } assertThat(state.currentTransition).isNull() - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // Swipe by half of verticalSwipeDistance. rule.onRoot().performTouchInput { @@ -643,7 +643,7 @@ class ElementTest { // Scroll 150% (Scene B overscroll by 50%) assertThat(transition.progress).isEqualTo(1.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f) // animatedFloat cannot overflow (canOverflow = false) assertThat(animatedFloat).isEqualTo(100f) @@ -655,7 +655,7 @@ class ElementTest { // Scroll 250% (Scene B overscroll by 150%) assertThat(transition.progress).isEqualTo(2.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) assertThat(animatedFloat).isEqualTo(100f) } @@ -707,7 +707,7 @@ class ElementTest { } assertThat(state.currentTransition).isNull() - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) fooElement.assertTopPositionInRootIsEqualTo(0.dp) @@ -720,7 +720,7 @@ class ElementTest { } val transition = state.currentTransition - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() assertThat(transition).isNotNull() assertThat(transition!!.progress).isEqualTo(-0.5f) fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f) @@ -732,7 +732,7 @@ class ElementTest { // Scroll 150% (Scene B overscroll by 50%) assertThat(transition.progress).isEqualTo(-1.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) } @@ -771,7 +771,7 @@ class ElementTest { // Scroll 150% (100% scroll + 50% overscroll) assertThat(transition!!.progress).isEqualTo(1.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f) assertThat(animatedFloat).isEqualTo(100f) @@ -782,7 +782,7 @@ class ElementTest { // Scroll 250% (100% scroll + 150% overscroll) assertThat(transition.progress).isEqualTo(2.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f) assertThat(animatedFloat).isEqualTo(100f) } @@ -828,7 +828,7 @@ class ElementTest { // Scroll 150% (100% scroll + 50% overscroll) assertThat(transition.progress).isEqualTo(1.5f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f)) assertThat(animatedFloat).isEqualTo(100f) @@ -840,7 +840,7 @@ class ElementTest { rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f } assertThat(transition.progress).isLessThan(1f) - assertThat(state.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() assertThat(transition.bouncingScene).isEqualTo(transition.toScene) assertThat(animatedFloat).isEqualTo(100f) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 9baabc3cfb57..93e94f8f95a2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -16,6 +16,7 @@ package com.android.compose.animation.scene +import android.util.Log import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.test.junit4.createComposeRule @@ -28,9 +29,12 @@ import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin -import kotlinx.coroutines.job import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -271,11 +275,21 @@ class SceneTransitionLayoutStateTest { } @Test - fun linkedTransition_startsLinkButLinkedStateIsTakenOver() { + fun linkedTransition_startsLinkButLinkedStateIsTakenOver() = runTest { val (parentState, childState) = setupLinkedStates() - val childTransition = transition(SceneA, SceneB) - val parentTransition = transition(SceneC, SceneA) + val childTransition = + transition( + SceneA, + SceneB, + onFinish = { launch { /* Do nothing. */} }, + ) + val parentTransition = + transition( + SceneC, + SceneA, + onFinish = { launch { /* Do nothing. */} }, + ) childState.startTransition(childTransition, null) parentState.startTransition(parentTransition, null) @@ -303,7 +317,7 @@ class SceneTransitionLayoutStateTest { // Default transition from A to B. assertThat(state.setTargetScene(SceneB, coroutineScope = this)).isNotNull() - assertThat(state.transformationSpec.transformations).hasSize(1) + assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1) // Go back to A. state.setTargetScene(SceneA, coroutineScope = this) @@ -320,14 +334,14 @@ class SceneTransitionLayoutStateTest { ) ) .isNotNull() - assertThat(state.transformationSpec.transformations).hasSize(2) + assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2) } @Test fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) state.startTransition( - transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.2f }), + transition(from = SceneA, to = SceneB, progress = { 0.2f }), transitionKey = null ) assertThat(state.isTransitioning()).isTrue() @@ -346,7 +360,7 @@ class SceneTransitionLayoutStateTest { fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest { val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) state.startTransition( - transition(from = SceneA, to = TestScenes.SceneB, progress = { 0.8f }), + transition(from = SceneA, to = SceneB, progress = { 0.8f }), transitionKey = null ) assertThat(state.isTransitioning()).isTrue() @@ -358,7 +372,35 @@ class SceneTransitionLayoutStateTest { // Go to the final scene if it is close to 1. assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() assertThat(state.isTransitioning()).isFalse() - assertThat(state.transitionState).isEqualTo(TransitionState.Idle(TestScenes.SceneB)) + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB)) + } + + @Test + fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty) + + val aToB = + transition( + from = SceneA, + to = SceneB, + progress = { 0.5f }, + onFinish = { launch { /* do nothing */} }, + ) + state.startTransition(aToB, transitionKey = null) + assertThat(state.currentTransitions).containsExactly(aToB).inOrder() + + val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f }) + state.startTransition(bToC, transitionKey = null) + assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder() + + // Ignore the request if the progress is not close to 0 or 1, using the threshold. + assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse() + assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder() + + // Go to the final scene if it is close to 1. + assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue() + assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC)) + assertThat(state.currentTransitions).isEmpty() } @Test @@ -435,23 +477,23 @@ class SceneTransitionLayoutStateTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } ) - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneA is NOT defined progress.value = -0.1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() progress.value = 1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneB is defined progress.value = 1.1f - assertThat(state.currentOverscrollSpec).isNotNull() - assertThat(state.currentOverscrollSpec?.scene).isEqualTo(SceneB) + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneB) } @Test @@ -465,23 +507,23 @@ class SceneTransitionLayoutStateTest { overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } } ) - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneA is defined progress.value = -0.1f - assertThat(state.currentOverscrollSpec).isNotNull() - assertThat(state.currentOverscrollSpec?.scene).isEqualTo(SceneA) + assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneA) // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() progress.value = 1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneB is NOT defined progress.value = 1.1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() } @Test @@ -492,21 +534,99 @@ class SceneTransitionLayoutStateTest { progress = { progress.value }, sceneTransitions = transitions {} ) - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneA is NOT defined progress.value = -0.1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() progress.value = 1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() // overscroll for SceneB is NOT defined progress.value = 1.1f - assertThat(state.currentOverscrollSpec).isNull() + assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + } + + @Test + fun multipleTransitions() = runTest { + val finishingTransitions = mutableSetOf<TransitionState.Transition>() + fun onFinish(transition: TransitionState.Transition): Job { + // Instead of letting the transition finish, we put the transition in the + // finishingTransitions set so that we can verify that finish() is called when expected + // and then we call state STLState.finishTransition() ourselves. + finishingTransitions.add(transition) + + return backgroundScope.launch { + // Try to acquire a locked mutex so that this code never completes. + Mutex(locked = true).withLock {} + } + } + + val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions) + val aToB = transition(SceneA, SceneB, onFinish = ::onFinish) + val bToC = transition(SceneB, SceneC, onFinish = ::onFinish) + val cToA = transition(SceneC, SceneA, onFinish = ::onFinish) + + // Starting state. + assertThat(finishingTransitions).isEmpty() + assertThat(state.currentTransitions).isEmpty() + + // A => B. + state.startTransition(aToB, transitionKey = null) + assertThat(finishingTransitions).isEmpty() + assertThat(state.finishedTransitions).isEmpty() + assertThat(state.currentTransitions).containsExactly(aToB).inOrder() + + // B => C. This should automatically call finish() on aToB. + state.startTransition(bToC, transitionKey = null) + assertThat(finishingTransitions).containsExactly(aToB) + assertThat(state.finishedTransitions).isEmpty() + assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder() + + // C => A. This should automatically call finish() on bToC. + state.startTransition(cToA, transitionKey = null) + assertThat(finishingTransitions).containsExactly(aToB, bToC) + assertThat(state.finishedTransitions).isEmpty() + assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder() + + // Mark bToC as finished. The list of current transitions does not change because aToB is + // still not marked as finished. + state.finishTransition(bToC, idleScene = bToC.currentScene) + assertThat(state.finishedTransitions).containsExactly(bToC, bToC.currentScene) + assertThat(state.currentTransitions).containsExactly(aToB, bToC, cToA).inOrder() + + // Mark aToB as finished. This will remove both aToB and bToC from the list of transitions. + state.finishTransition(aToB, idleScene = aToB.currentScene) + assertThat(state.finishedTransitions).isEmpty() + assertThat(state.currentTransitions).containsExactly(cToA).inOrder() + } + + @Test + fun tooManyTransitionsLogsWtfAndClearsTransitions() = runTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions) + + fun startTransition() { + val transition = transition(SceneA, SceneB, onFinish = { launch { /* do nothing */} }) + state.startTransition(transition, transitionKey = null) + } + + var hasLoggedWtf = false + val originalHandler = Log.setWtfHandler { _, _, _ -> hasLoggedWtf = true } + try { + repeat(100) { startTransition() } + assertThat(hasLoggedWtf).isFalse() + assertThat(state.currentTransitions).hasSize(100) + + startTransition() + assertThat(hasLoggedWtf).isTrue() + assertThat(state.currentTransitions).hasSize(1) + } finally { + Log.setWtfHandler(originalHandler) + } } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index efaea71f8d2c..723a1825f205 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -299,6 +299,11 @@ class SceneTransitionLayoutTest { .isWithin(DpOffsetSubject.DefaultTolerance) .of(DpOffset(expectedOffset, expectedOffset)) + // Wait for the transition to C to finish. + rule.mainClock.advanceTimeBy(TestTransitionDuration) + assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) + assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + // Go back to scene A. This should happen instantly (once the animation started, i.e. after // 2 frames) given that we use a snap() animation spec. currentScene = TestScenes.SceneA diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 99372a5d084b..f034c184b794 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -547,12 +547,12 @@ class SwipeToSceneTest { } assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() - assertThat(state.transformationSpec.transformations).hasSize(1) + assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1) // Move the pointer up to swipe to scene B using the new transition. rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) } assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() - assertThat(state.transformationSpec.transformations).hasSize(2) + assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2) } @Test diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt index a32fe2273804..767057b585b8 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt @@ -29,6 +29,7 @@ fun transition( isUpOrLeft: Boolean = false, bouncingScene: SceneKey? = null, orientation: Orientation = Orientation.Horizontal, + onFinish: ((TransitionState.Transition) -> Job)? = null, ): TransitionState.Transition { return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { override val currentScene: SceneKey = from @@ -46,7 +47,13 @@ fun transition( } override fun finish(): Job { - error("finish() is not supported in test transitions") + val onFinish = + onFinish + ?: error( + "onFinish() must be provided if finish() is called on test transitions" + ) + + return onFinish(this) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index 69ff5ab3d84d..b4f87c47a0b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -21,6 +21,8 @@ import android.content.Intent import android.view.View import android.widget.FrameLayout import android.widget.RemoteViews.RemoteResponse +import androidx.core.util.component1 +import androidx.core.util.component2 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -29,6 +31,7 @@ import com.android.systemui.util.mockito.eq import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.refEq import org.mockito.Mock import org.mockito.Mockito.isNull import org.mockito.Mockito.notNull @@ -62,6 +65,7 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { val parent = FrameLayout(context) val view = CommunalAppWidgetHostView(context) parent.addView(view) + val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) underTest.onInteraction(view, testIntent, testResponse) @@ -70,6 +74,8 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { eq(testIntent), isNull(), notNull(), + refEq(fillInIntent), + refEq(activityOptions.toBundle()), ) } @@ -78,10 +84,17 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { val parent = FrameLayout(context) val view = View(context) parent.addView(view) + val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) underTest.onInteraction(view, testIntent, testResponse) verify(activityStarter) - .startPendingIntentMaybeDismissingKeyguard(eq(testIntent), isNull(), isNull()) + .startPendingIntentMaybeDismissingKeyguard( + eq(testIntent), + isNull(), + isNull(), + refEq(fillInIntent), + refEq(activityOptions.toBundle()), + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index 8aa0e3fc4d23..c8062fb4e724 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.phone +import android.app.ActivityOptions import android.app.PendingIntent import android.content.Intent +import android.os.Bundle import android.os.RemoteException import android.os.UserHandle import android.view.View import android.widget.FrameLayout +import android.window.SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -48,6 +51,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever @@ -173,6 +177,53 @@ class ActivityStarterImplTest : SysuiTestCase() { ) } + fun startPendingIntentDismissingKeyguard_fillInIntentAndExtraOptions_sendAndReturnResult() { + val pendingIntent = mock(PendingIntent::class.java) + val fillInIntent = mock(Intent::class.java) + val parent = FrameLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) + val controller = ActivityTransitionAnimator.Controller.fromView(view) + whenever(pendingIntent.isActivity).thenReturn(true) + whenever(keyguardStateController.isShowing).thenReturn(true) + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt())) + .thenReturn(false) + + // extra activity options to set on pending intent + val activityOptions = mock(ActivityOptions::class.java) + activityOptions.splashScreenStyle = SPLASH_SCREEN_STYLE_SOLID_COLOR + activityOptions.isPendingIntentBackgroundActivityLaunchAllowedByPermission = false + val bundleCaptor = argumentCaptor<Bundle>() + + underTest.startPendingIntentMaybeDismissingKeyguard( + intent = pendingIntent, + animationController = controller, + intentSentUiThreadCallback = null, + fillInIntent = fillInIntent, + extraOptions = activityOptions.toBundle(), + ) + mainExecutor.runAllReady() + + // Fill-in intent is passed and options contain extra values specified + verify(pendingIntent) + .sendAndReturnResult( + eq(context), + eq(0), + eq(fillInIntent), + nullable(), + nullable(), + nullable(), + bundleCaptor.capture() + ) + val options = ActivityOptions.fromBundle(bundleCaptor.value) + assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission).isFalse() + assertThat(options.splashScreenStyle).isEqualTo(SPLASH_SCREEN_STYLE_SOLID_COLOR) + } + @Test fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() { val pendingIntent = mock(PendingIntent::class.java) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 1126ec3382a4..072ec9986c61 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -17,6 +17,7 @@ package com.android.systemui.plugins; import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Intent; +import android.os.Bundle; import android.os.UserHandle; import android.view.View; @@ -67,6 +68,17 @@ public interface ActivityStarter { @Nullable ActivityTransitionAnimator.Controller animationController); /** + * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable, + * ActivityTransitionAnimator.Controller)}, but also specifies a fill-in intent and extra + * options that could be used to populate the pending intent and launch the activity. + */ + void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, + @Nullable Runnable intentSentUiThreadCallback, + @Nullable ActivityTransitionAnimator.Controller animationController, + @Nullable Intent fillInIntent, + @Nullable Bundle extraOptions); + + /** * The intent flag can be specified in startActivity(). */ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 4c1e77bc47f8..778d8cf56648 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -16,9 +16,14 @@ package com.android.systemui.communal.widgets +import android.app.ActivityOptions import android.app.PendingIntent +import android.content.Intent +import android.util.Pair import android.view.View import android.widget.RemoteViews +import androidx.core.util.component1 +import androidx.core.util.component2 import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.view.getNearestParent import com.android.systemui.plugins.ActivityStarter @@ -33,21 +38,33 @@ constructor( view: View, pendingIntent: PendingIntent, response: RemoteViews.RemoteResponse - ): Boolean = - when { - pendingIntent.isActivity -> startActivity(view, pendingIntent) - else -> - RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)) + ): Boolean { + val launchOptions = response.getLaunchOptions(view) + return when { + pendingIntent.isActivity -> + // Forward the fill-in intent and activity options retrieved from the response + // to populate the pending intent, so that list items can launch respective + // activities. + startActivity(view, pendingIntent, launchOptions) + else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions) } + } - private fun startActivity(view: View, pendingIntent: PendingIntent): Boolean { + private fun startActivity( + view: View, + pendingIntent: PendingIntent, + launchOptions: Pair<Intent, ActivityOptions>, + ): Boolean { val hostView = view.getNearestParent<CommunalAppWidgetHostView>() val animationController = hostView?.let(ActivityTransitionAnimator.Controller::fromView) + val (fillInIntent, activityOptions) = launchOptions activityStarter.startPendingIntentMaybeDismissingKeyguard( pendingIntent, /* intentSentUiThreadCallback = */ null, - animationController + animationController, + fillInIntent, + activityOptions.toBundle(), ) return true } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 18d2f306c247..b0707db0d02d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -111,7 +111,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Override protected void handleClick(@Nullable View view) { if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) { - mDialogViewModel.showDialog(mContext, view); + mDialogViewModel.showDialog(view); } else { // Secondary clicks are header clicks, just toggle. final boolean isEnabled = mState.value; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt index 1247854da61d..59fc81c82df0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt @@ -19,8 +19,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth import android.util.Log import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map /** Interactor class responsible for interacting with the Bluetooth Auto-On feature. */ @SysUISingleton @@ -30,14 +28,10 @@ constructor( private val bluetoothAutoOnRepository: BluetoothAutoOnRepository, ) { - val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged() + val isEnabled = bluetoothAutoOnRepository.isAutoOn - /** - * Checks if the auto on value is present in the repository. - * - * @return `true` if a value is present (i.e, the feature is enabled by the Bluetooth server). - */ - suspend fun isValuePresent(): Boolean = bluetoothAutoOnRepository.isValuePresent() + /** Checks if the auto on feature is supported. */ + suspend fun isAutoOnSupported(): Boolean = bluetoothAutoOnRepository.isAutoOnSupported() /** * Sets enabled or disabled based on the provided value. @@ -45,17 +39,14 @@ constructor( * @param value `true` to enable the feature, `false` to disable it. */ suspend fun setEnabled(value: Boolean) { - if (!isValuePresent()) { + if (!isAutoOnSupported()) { Log.e(TAG, "Trying to set toggle value while feature not available.") } else { - val newValue = if (value) ENABLED else DISABLED - bluetoothAutoOnRepository.setAutoOn(newValue) + bluetoothAutoOnRepository.setAutoOn(value) } } companion object { private const val TAG = "BluetoothAutoOnInteractor" - const val DISABLED = 0 - const val ENABLED = 1 } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt index f97fc389b12c..9ee582a77862 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt @@ -16,22 +16,23 @@ package com.android.systemui.qs.tiles.dialog.bluetooth +import android.bluetooth.BluetoothAdapter +import android.util.Log +import com.android.settingslib.bluetooth.BluetoothCallback +import com.android.settingslib.bluetooth.LocalBluetoothManager +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.user.data.repository.UserRepository -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -44,61 +45,87 @@ import kotlinx.coroutines.withContext class BluetoothAutoOnRepository @Inject constructor( - private val secureSettings: SecureSettings, - private val userRepository: UserRepository, + localBluetoothManager: LocalBluetoothManager?, + private val bluetoothAdapter: BluetoothAdapter?, @Application private val coroutineScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, ) { - // Flow representing the auto on setting value for the current user - @OptIn(ExperimentalCoroutinesApi::class) - internal val isAutoOn: StateFlow<Int> = - userRepository.selectedUserInfo - .flatMapLatest { userInfo -> - secureSettings - .observerFlow(userInfo.id, SETTING_NAME) - .onStart { emit(Unit) } - .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) } - } - .distinctUntilChanged() - .flowOn(backgroundDispatcher) - .stateIn( - coroutineScope, - SharingStarted.WhileSubscribed(replayExpirationMillis = 0), - UNSET - ) + // Flow representing the auto on state for the current user + internal val isAutoOn: Flow<Boolean> = + localBluetoothManager?.eventManager?.let { eventManager -> + conflatedCallbackFlow { + val listener = + object : BluetoothCallback { + override fun onAutoOnStateChanged(autoOnState: Int) { + super.onAutoOnStateChanged(autoOnState) + if ( + autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED || + autoOnState == BluetoothAdapter.AUTO_ON_STATE_DISABLED + ) { + trySendWithFailureLogging( + autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED, + TAG, + "onAutoOnStateChanged" + ) + } + } + } + eventManager.registerCallback(listener) + awaitClose { eventManager.unregisterCallback(listener) } + } + .onStart { emit(isAutoOnEnabled()) } + .flowOn(backgroundDispatcher) + .stateIn( + coroutineScope, + SharingStarted.WhileSubscribed(replayExpirationMillis = 0), + initialValue = false + ) + } + ?: flowOf(false) /** - * Checks if the auto on setting value is ever set for the current user. + * Checks if the auto on feature is supported for the current user. * - * @return `true` if the setting value is not UNSET, `false` otherwise. + * @throws Exception if an error occurs while checking auto-on support. */ - suspend fun isValuePresent(): Boolean = + suspend fun isAutoOnSupported(): Boolean = withContext(backgroundDispatcher) { - secureSettings.getIntForUser( - SETTING_NAME, - UNSET, - userRepository.getSelectedUserInfo().id - ) != UNSET + try { + bluetoothAdapter?.isAutoOnSupported ?: false + } catch (e: Exception) { + // Server could throw TimeoutException, InterruptedException or ExecutionException + Log.e(TAG, "Error calling isAutoOnSupported", e) + false + } } - /** - * Sets the Bluetooth Auto-On setting value for the current user. - * - * @param value The new setting value to be applied. - */ - suspend fun setAutoOn(value: Int) { + /** Sets the Bluetooth Auto-On for the current user. */ + suspend fun setAutoOn(value: Boolean) { withContext(backgroundDispatcher) { - secureSettings.putIntForUser( - SETTING_NAME, - value, - userRepository.getSelectedUserInfo().id - ) + try { + bluetoothAdapter?.setAutoOnEnabled(value) + } catch (e: Exception) { + // Server could throw IllegalStateException, TimeoutException, InterruptedException + // or ExecutionException + Log.e(TAG, "Error calling setAutoOnEnabled", e) + } } } - companion object { - const val SETTING_NAME = "bluetooth_automatic_turn_on" - const val UNSET = -1 + private suspend fun isAutoOnEnabled() = + withContext(backgroundDispatcher) { + try { + bluetoothAdapter?.isAutoOnEnabled ?: false + } catch (e: Exception) { + // Server could throw IllegalStateException, TimeoutException, InterruptedException + // or ExecutionException + Log.e(TAG, "Error calling isAutoOnEnabled", e) + false + } + } + + private companion object { + const val TAG = "BluetoothAutoOnRepository" } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt index 9d5370354fe8..a8d9e781228b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -58,7 +57,6 @@ import kotlinx.coroutines.withContext class BluetoothTileDialogDelegate @AssistedInject internal constructor( - @Assisted private val context: Context, @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties, @Assisted private val cachedContentHeight: Int, @Assisted private val bluetoothToggleInitialValue: Boolean, @@ -69,11 +67,8 @@ internal constructor( private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, private val systemuiDialogFactory: SystemUIDialog.Factory, - mainLayoutInflater: LayoutInflater, ) : SystemUIDialog.Delegate { - private val layoutInflater = mainLayoutInflater.cloneInContext(context) - private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> = MutableStateFlow(bluetoothToggleInitialValue) internal val bluetoothStateToggle @@ -102,7 +97,6 @@ internal constructor( @AssistedFactory internal interface Factory { fun create( - context: Context, initialUiProperties: BluetoothTileDialogViewModel.UiProperties, cachedContentHeight: Int, bluetoothEnabled: Boolean, @@ -112,16 +106,15 @@ internal constructor( } override fun createDialog(): SystemUIDialog { - val dialog = systemuiDialogFactory.create(this, context) - - return dialog + return systemuiDialogFactory.create(this) } override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { SystemUIDialog.registerDismissListener(dialog, dismissListener) uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN) + val context = dialog.context - layoutInflater.inflate(R.layout.bluetooth_tile_dialog, null).apply { + LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null).apply { accessibilityPaneTitle = context.getText(R.string.accessibility_desc_quick_settings) dialog.setContentView(this) } @@ -201,7 +194,7 @@ internal constructor( setEnabled(true) alpha = ENABLED_ALPHA } - getSubtitleTextView(dialog).text = context.getString(uiProperties.subTitleResId) + getSubtitleTextView(dialog).text = dialog.context.getString(uiProperties.subTitleResId) getAutoOnToggleView(dialog).visibility = uiProperties.autoOnToggleVisibility } @@ -215,7 +208,7 @@ internal constructor( setEnabled(true) alpha = ENABLED_ALPHA } - getAutoOnToggleInfoTextView(dialog).text = context.getString(infoResId) + getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId) } private fun setupToggle(dialog: SystemUIDialog) { @@ -288,7 +281,7 @@ internal constructor( private fun setupRecyclerView(dialog: SystemUIDialog) { getDeviceListView(dialog).apply { - layoutManager = LinearLayoutManager(context) + layoutManager = LinearLayoutManager(dialog.context) adapter = deviceItemAdapter } } @@ -343,7 +336,9 @@ internal constructor( private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder { - val view = layoutInflater.inflate(R.layout.bluetooth_device_item, parent, false) + val view = + LayoutInflater.from(parent.context) + .inflate(R.layout.bluetooth_device_item, parent, false) return DeviceItemViewHolder(view) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index e4f3c199371e..fd624d2f1ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth -import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.os.Bundle @@ -29,7 +28,6 @@ import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger -import com.android.settingslib.flags.Flags.bluetoothQsTileDialogAutoOnToggle import com.android.systemui.Prefs import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator @@ -78,19 +76,19 @@ constructor( /** * Shows the dialog. * - * @param context The context in which the dialog is displayed. * @param view The view from which the dialog is shown. */ @kotlinx.coroutines.ExperimentalCoroutinesApi - fun showDialog(context: Context, view: View?) { + fun showDialog(view: View?) { cancelJob() job = coroutineScope.launch(mainDispatcher) { var updateDeviceItemJob: Job? var updateDialogUiJob: Job? = null - val dialogDelegate = createBluetoothTileDialog(context) + val dialogDelegate = createBluetoothTileDialog() val dialog = dialogDelegate.createDialog() + val context = dialog.context view?.let { dialogTransitionAnimator.showFromView( @@ -213,7 +211,7 @@ constructor( } } - private suspend fun createBluetoothTileDialog(context: Context): BluetoothTileDialogDelegate { + private suspend fun createBluetoothTileDialog(): BluetoothTileDialogDelegate { val cachedContentHeight = withContext(backgroundDispatcher) { sharedPreferences.getInt( @@ -223,7 +221,6 @@ constructor( } return bluetoothDialogDelegateFactory.create( - context, UiProperties.build( bluetoothStateInteractor.isBluetoothEnabled, isAutoOnToggleFeatureAvailable() @@ -277,7 +274,7 @@ constructor( @VisibleForTesting internal suspend fun isAutoOnToggleFeatureAvailable() = - bluetoothQsTileDialogAutoOnToggle() && bluetoothAutoOnInteractor.isValuePresent() + bluetoothAutoOnInteractor.isAutoOnSupported() companion object { private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java index 06c0b8b6e769..c89b47612814 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java @@ -33,6 +33,7 @@ import android.content.Intent; import android.content.Intent.CaptureContentForNoteStatusCodes; import android.content.res.Resources; import android.os.IBinder; +import android.util.Log; import androidx.annotation.Nullable; @@ -58,6 +59,8 @@ import javax.inject.Inject; */ public class AppClipsService extends Service { + private static final String TAG = AppClipsService.class.getSimpleName(); + @Application private final Context mContext; private final FeatureFlags mFeatureFlags; private final Optional<Bubbles> mOptionalBubbles; @@ -77,14 +80,22 @@ public class AppClipsService extends Service { private boolean checkIndependentVariables() { if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) { + Log.d(TAG, "Feature flag disabled"); return false; } if (mOptionalBubbles.isEmpty()) { + Log.d(TAG, "Bubbles not available"); return false; } - return isComponentValid(); + if (isComponentValid()) { + Log.d(TAG, "checkIndependentVariables returned true"); + return true; + } + + Log.d(TAG, "checkIndependentVariables returned false"); + return false; } private boolean isComponentValid() { @@ -93,12 +104,27 @@ public class AppClipsService extends Service { componentName = ComponentName.unflattenFromString( mContext.getString(R.string.config_screenshotAppClipsActivityComponent)); } catch (Resources.NotFoundException e) { + Log.d(TAG, "AppClips activity component resource not defined"); + return false; + } + + if (componentName == null) { + Log.d(TAG, "AppClips component name not defined"); + return false; + } + + if (componentName.getPackageName().isEmpty()) { + Log.d(TAG, "AppClips component package name is empty"); + return false; + } + + if (componentName.getClassName().isEmpty()) { + Log.d(TAG, "AppClips component class name is empty"); return false; } - return componentName != null - && !componentName.getPackageName().isEmpty() - && !componentName.getClassName().isEmpty(); + Log.d(TAG, "isComponentValid returned true"); + return true; } @Nullable @@ -107,24 +133,39 @@ public class AppClipsService extends Service { return new IAppClipsService.Stub() { @Override public boolean canLaunchCaptureContentActivityForNote(int taskId) { - return canLaunchCaptureContentActivityForNoteInternal(taskId) - == CAPTURE_CONTENT_FOR_NOTE_SUCCESS; + if (canLaunchCaptureContentActivityForNoteInternal(taskId) + == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) { + Log.d(TAG, String.format("Can launch AppClips returned true for %d", taskId)); + return true; + } + + Log.d(TAG, String.format("Can launch AppClips returned false for %d", taskId)); + return false; } @Override @CaptureContentForNoteStatusCodes public int canLaunchCaptureContentActivityForNoteInternal(int taskId) { if (!mAreTaskAndTimeIndependentPrerequisitesMet) { + Log.d(TAG, + String.format("Task (%d) and time independent prereqs not met", taskId)); return CAPTURE_CONTENT_FOR_NOTE_FAILED; } if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) { + Log.d(TAG, String.format("Taskid %d is not app bubble task", taskId)); return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED; } - return mDevicePolicyManager.getScreenCaptureDisabled(null) - ? CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN - : CAPTURE_CONTENT_FOR_NOTE_SUCCESS; + if (mDevicePolicyManager.getScreenCaptureDisabled(null)) { + Log.d(TAG, + String.format("Screen capture disabled by admin, taskId %d", taskId)); + return CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN; + } + + Log.d(TAG, + String.format("Can launch AppClips (internal) successful for %d", taskId)); + return CAPTURE_CONTENT_FOR_NOTE_SUCCESS; } }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 0c69a65b96af..8531eaa46804 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -22,6 +22,7 @@ import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable +import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager @@ -59,6 +60,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -260,8 +262,11 @@ constructor( } } - private suspend fun trackUnseenFilterSettingChanges() { - secureSettings + private fun unseenFeatureEnabled(): Flow<Boolean> { + if (notificationMinimalismPrototype()) { + return flowOf(true) + } + return secureSettings // emit whenever the setting has changed .observerFlow( UserHandle.USER_ALL, @@ -283,17 +288,20 @@ constructor( // only track the most recent emission, if events are happening faster than they can be // consumed .conflate() - .collectLatest { setting -> - // update local field and invalidate if necessary - if (setting != unseenFilterEnabled) { - unseenFilterEnabled = setting - unseenNotifFilter.invalidateList("unseen setting changed") - } - // if the setting is enabled, then start tracking and filtering unseen notifications - if (setting) { - trackSeenNotifications() - } + } + + private suspend fun trackUnseenFilterSettingChanges() { + unseenFeatureEnabled().collectLatest { setting -> + // update local field and invalidate if necessary + if (setting != unseenFilterEnabled) { + unseenFilterEnabled = setting + unseenNotifFilter.invalidateList("unseen setting changed") } + // if the setting is enabled, then start tracking and filtering unseen notifications + if (setting) { + trackSeenNotifications() + } + } } private val collectionListener = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index f792898520a2..adcbbfbde002 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -58,6 +58,7 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mClearAllButton; private FooterViewButton mManageOrHistoryButton; + private boolean mShouldBeHidden; private boolean mShowHistory; // String cache, for performance reasons. // Reading them from a Resources object can be quite slow sometimes. @@ -110,6 +111,20 @@ public class FooterView extends StackScrollerDecorView { setSecondaryVisible(visible, animate, onAnimationEnded); } + /** See {@link this#setShouldBeHidden} below. */ + public boolean shouldBeHidden() { + return mShouldBeHidden; + } + + /** + * Whether this view's visibility should be set to INVISIBLE. Note that this is different from + * the {@link StackScrollerDecorView#setVisible} method, which in turn handles visibility + * transitions between VISIBLE and GONE. + */ + public void setShouldBeHidden(boolean hide) { + mShouldBeHidden = hide; + } + @Override public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index b8b4a03eae51..eb6c7b520037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1767,9 +1767,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public ExpandableNotificationRow(Context context, AttributeSet attrs) { this(context, attrs, context); - if (com.android.systemui.Flags.notificationRowUserContext()) { - Log.wtf(TAG, "This constructor shouldn't be called"); - } + Log.wtf(TAG, "This constructor shouldn't be called"); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index 609b15e51673..3e932aa616b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -31,7 +31,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageResolver; import com.android.internal.widget.LocalImageResolver; import com.android.internal.widget.MessagingMessage; -import com.android.systemui.Flags; import java.util.HashSet; import java.util.List; @@ -67,11 +66,7 @@ public class NotificationInlineImageResolver implements ImageResolver { * @param imageCache The implementation of internal cache. */ public NotificationInlineImageResolver(Context context, ImageCache imageCache) { - if (Flags.notificationRowUserContext()) { - mContext = context; - } else { - mContext = context.getApplicationContext(); - } + mContext = context; mImageCache = imageCache; if (mImageCache != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java index ea3036e35c1b..5fbcebda7cd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java @@ -66,9 +66,7 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf mInflateOrigin = new Throwable("inflate requested here"); } mListener = listener; - AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext() - ? new AsyncLayoutInflater(context, makeRowInflater(entry)) - : new AsyncLayoutInflater(context); + AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, makeRowInflater(entry)); mEntry = entry; entry.setInflationTask(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 2d9c63efee53..1b53cbed8354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -20,6 +20,7 @@ import android.content.res.Resources import android.util.Log import android.view.View.GONE import androidx.annotation.VisibleForTesting +import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -66,6 +67,11 @@ constructor( */ private var maxKeyguardNotifications by notNull<Int>() + /** + * Whether [maxKeyguardNotifications] will have 1 added to it when media is shown in the stack. + */ + private var maxNotificationsExcludesMedia = false + /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */ private var dividerHeight by notNull<Float>() @@ -168,7 +174,11 @@ constructor( log { "\n" } val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight) + + // TODO: Avoid making this split shade assumption by simply checking the stack for media val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation() + val isMediaShowingInStack = isMediaShowing && !splitShadeStateController + .shouldUseSplitNotificationShade(resources) log { "\tGet maxNotifWithoutSavingSpace ---" } val maxNotifWithoutSavingSpace = @@ -181,12 +191,11 @@ constructor( } // How many notifications we can show at heightWithoutLockscreenConstraints - var minCountAtHeightWithoutConstraints = - if (isMediaShowing && !splitShadeStateController - .shouldUseSplitNotificationShade(resources)) 2 else 1 + val minCountAtHeightWithoutConstraints = if (isMediaShowingInStack) 2 else 1 log { "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " + "isMediaShowing=$isMediaShowing" + + "isMediaShowingInStack=$isMediaShowingInStack" + "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints" } log { "\n" } @@ -223,7 +232,9 @@ constructor( } if (onLockscreen()) { - maxNotifications = min(maxKeyguardNotifications, maxNotifications) + val increaseMaxForMedia = maxNotificationsExcludesMedia && isMediaShowingInStack + val lockscreenMax = maxKeyguardNotifications.safeIncrementIf(increaseMaxForMedia) + maxNotifications = min(lockscreenMax, maxNotifications) } // Could be < 0 if the space available is less than the shelf size. Returns 0 in this case. @@ -276,7 +287,7 @@ constructor( height = notifsHeight + shelfHeightWithSpaceBefore log { "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" + - " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" + + " -> $height=($notifsHeight+$shelfHeightWithSpaceBefore)" + " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen" } } @@ -367,8 +378,9 @@ constructor( } fun updateResources() { - maxKeyguardNotifications = - infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count)) + maxKeyguardNotifications = if (notificationMinimalismPrototype()) 1 + else infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count)) + maxNotificationsExcludesMedia = notificationMinimalismPrototype() dividerHeight = max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat()) @@ -486,6 +498,13 @@ constructor( v } + private fun Int.safeIncrementIf(condition: Boolean): Int = + if (condition && this != Int.MAX_VALUE) { + this + 1 + } else { + this + } + /** Returns the last index where [predicate] returns true, or -1 if it was always false. */ private fun <T> Sequence<T>.lastIndexWhile(predicate: (T) -> Boolean): Int = takeWhile(predicate).count() - 1 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index b42c07d2c93c..5eaccd924344 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -594,15 +594,16 @@ public class StackScrollAlgorithm { ); if (view instanceof FooterView) { if (FooterViewRefactor.isEnabled()) { - final float footerEnd = algorithmState.mCurrentExpandedYPosition - + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); - // TODO(b/293167744): May be able to keep only noSpaceForFooter here if we add an - // emission when clearAllNotifications is called, and then use that in the footer - // visibility flow. - ((FooterView.FooterViewState) viewState).hideContent = - noSpaceForFooter || (ambientState.isClearAllInProgress() - && !hasNonClearableNotifs(algorithmState)); + if (((FooterView) view).shouldBeHidden()) { + viewState.hidden = true; + } else { + final float footerEnd = algorithmState.mCurrentExpandedYPosition + + view.getIntrinsicHeight(); + final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); + ((FooterView.FooterViewState) viewState).hideContent = + noSpaceForFooter || (ambientState.isClearAllInProgress() + && !hasNonClearableNotifs(algorithmState)); + } } else { final boolean shadeClosed = !ambientState.isShadeExpanded(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index 97cbbe86389b..18bb51197555 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -193,13 +193,14 @@ constructor( }, ) launch { - viewModel.shouldShowFooterView.collect { animatedVisibility -> + viewModel.shouldIncludeFooterView.collect { animatedVisibility -> footerView.setVisible( /* visible = */ animatedVisibility.value, /* animate = */ animatedVisibility.isAnimating, ) } } + launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } } disposableHandle.awaitCancellationThenDispose() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index a6ca027d6dbb..5a7433d3579b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -31,7 +31,6 @@ import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor -import com.android.systemui.util.kotlin.combine import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent import com.android.systemui.util.ui.AnimatedValue @@ -111,7 +110,32 @@ constructor( } } - val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy { + /** + * Whether the footer should not be visible for the user, even if it's present in the list (as + * per [shouldIncludeFooterView] below). + * + * This essentially corresponds to having the view set to INVISIBLE. + */ + val shouldHideFooterView: Flow<Boolean> by lazy { + if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { + flowOf(false) + } else { + // When the shade is closed, the footer is still present in the list, but not visible. + // This prevents the footer from being shown when a HUN is present, while still allowing + // the footer to be counted as part of the shade for measurements. + shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged() + } + } + + /** + * Whether the footer should be part of the list or not, and whether the transition from one + * state to another should be animated. This essentially corresponds to transitioning the view + * visibility from VISIBLE to GONE and vice versa. + * + * Note that this value being true doesn't necessarily mean that the footer is visible. It could + * be hidden by another condition (see [shouldHideFooterView] above). + */ + val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { flowOf(AnimatedValue.NotAnimating(false)) } else { @@ -120,34 +144,30 @@ constructor( userSetupInteractor.isUserSetUp, notificationStackInteractor.isShowingOnLockscreen, shadeInteractor.isQsFullscreen, - remoteInputInteractor.isRemoteInputActive, - shadeInteractor.shadeExpansion.map { it == 0f }.distinctUntilChanged(), + remoteInputInteractor.isRemoteInputActive ) { hasNotifications, isUserSetUp, isShowingOnLockscreen, qsFullScreen, - isRemoteInputActive, - isShadeClosed -> + isRemoteInputActive -> when { - !hasNotifications -> VisibilityChange.HIDE_WITH_ANIMATION + !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer until the user setup is complete, to prevent access // to settings (b/193149550). - !isUserSetUp -> VisibilityChange.HIDE_WITH_ANIMATION + !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Do not show the footer if the lockscreen is visible (incl. AOD), // except if the shade is opened on top. See also b/219680200. // Do not animate, as that makes the footer appear briefly when // transitioning between the shade and keyguard. - isShowingOnLockscreen -> VisibilityChange.HIDE_WITHOUT_ANIMATION + isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION // Do not show the footer if quick settings are fully expanded (except // for the foldable split shade view). See b/201427195 && b/222699879. - qsFullScreen -> VisibilityChange.HIDE_WITH_ANIMATION + qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION // Hide the footer if remote input is active (i.e. user is replying to a // notification). See b/75984847. - isRemoteInputActive -> VisibilityChange.HIDE_WITH_ANIMATION - // Never show the footer if the shade is collapsed (e.g. when HUNing). - isShadeClosed -> VisibilityChange.HIDE_WITHOUT_ANIMATION - else -> VisibilityChange.SHOW_WITH_ANIMATION + isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + else -> VisibilityChange.APPEAR_WITH_ANIMATION } } .flowOn(bgDispatcher) @@ -180,9 +200,9 @@ constructor( } enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) { - HIDE_WITHOUT_ANIMATION(visible = false, canAnimate = false), - HIDE_WITH_ANIMATION(visible = false, canAnimate = true), - SHOW_WITH_ANIMATION(visible = true, canAnimate = true) + DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false), + DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true), + APPEAR_WITH_ANIMATION(visible = true, canAnimate = true) } // TODO(b/308591475): This should be tracked separately by the empty shade. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index a55de251314f..37646aea86e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -21,6 +21,7 @@ import android.app.PendingIntent import android.app.TaskStackBuilder import android.content.Context import android.content.Intent +import android.os.Bundle import android.os.RemoteException import android.os.UserHandle import android.provider.Settings @@ -149,6 +150,23 @@ constructor( ) } + override fun startPendingIntentMaybeDismissingKeyguard( + intent: PendingIntent, + intentSentUiThreadCallback: Runnable?, + animationController: ActivityTransitionAnimator.Controller?, + fillInIntent: Intent?, + extraOptions: Bundle?, + ) { + activityStarterInternal.startPendingIntentDismissingKeyguard( + intent = intent, + intentSentUiThreadCallback = intentSentUiThreadCallback, + animationController = animationController, + showOverLockscreen = true, + fillInIntent = fillInIntent, + extraOptions = extraOptions, + ) + } + /** * TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate * this. @@ -554,6 +572,8 @@ constructor( associatedView: View? = null, animationController: ActivityTransitionAnimator.Controller? = null, showOverLockscreen: Boolean = false, + fillInIntent: Intent? = null, + extraOptions: Bundle? = null, ) { val animationController = if (associatedView is ExpandableNotificationRow) { @@ -614,9 +634,10 @@ constructor( val options = ActivityOptions( CentralSurfaces.getActivityOptions( - displayId, - animationAdapter - ) + displayId, + animationAdapter + ) + .apply { extraOptions?.let { putAll(it) } } ) // TODO b/221255671: restrict this to only be set for // notifications @@ -625,9 +646,9 @@ constructor( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED ) return intent.sendAndReturnResult( - null, + context, 0, - null, + fillInIntent, null, null, null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index 60b8599ecabd..b085d8046b12 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -301,7 +301,7 @@ class FullMobileConnectionRepository( .flatMapLatest { it.networkName } .logDiffsForTable( tableLogBuffer, - columnPrefix = "", + columnPrefix = "intent", initialValue = activeRepo.value.networkName.value, ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value) @@ -311,7 +311,7 @@ class FullMobileConnectionRepository( .flatMapLatest { it.carrierName } .logDiffsForTable( tableLogBuffer, - columnPrefix = "", + columnPrefix = "sub", initialValue = activeRepo.value.carrierName.value, ) .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierName.value) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index f01ac0e0a677..5ab2ae899370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -358,7 +358,13 @@ class MobileConnectionRepositoryImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId) - /** BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here */ + /** + * BroadcastDispatcher does not handle sticky broadcasts, so we can't use it here. Note that we + * now use the [SharingStarted.Eagerly] strategy, because there have been cases where the sticky + * broadcast does not represent the correct state. + * + * See b/322432056 for context. + */ @SuppressLint("RegisterReceiverViaContext") override val networkName: StateFlow<NetworkNameModel> = conflatedCallbackFlow { @@ -388,7 +394,7 @@ class MobileConnectionRepositoryImpl( awaitClose { context.unregisterReceiver(receiver) } } .flowOn(bgDispatcher) - .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName) + .stateIn(scope, SharingStarted.Eagerly, defaultNetworkName) override val dataEnabled = run { val initial = telephonyManager.isDataConnectionAllowed diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt index 37107135c6d8..036d3c862ae0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt @@ -16,22 +16,25 @@ package com.android.systemui.qs.tiles.dialog.bluetooth -import android.content.pm.UserInfo +import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import kotlin.test.Test import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -41,8 +44,17 @@ class BluetoothAutoOnInteractorTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) - private var secureSettings: FakeSettings = FakeSettings() - private val userRepository: FakeUserRepository = FakeUserRepository() + private val bluetoothAdapter = + mock<BluetoothAdapter> { + var autoOn = false + whenever(isAutoOnEnabled).thenAnswer { autoOn } + + whenever(setAutoOnEnabled(anyBoolean())).thenAnswer { invocation -> + autoOn = invocation.getArgument(0) as Boolean + autoOn + } + } + @Mock private lateinit var localBluetoothManager: LocalBluetoothManager private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor @Before @@ -50,49 +62,35 @@ class BluetoothAutoOnInteractorTest : SysuiTestCase() { bluetoothAutoOnInteractor = BluetoothAutoOnInteractor( BluetoothAutoOnRepository( - secureSettings, - userRepository, + localBluetoothManager, + bluetoothAdapter, testScope.backgroundScope, - testDispatcher + testDispatcher, ) ) } @Test - fun testSet_bluetoothAutoOnUnset_doNothing() { + fun testSetEnabled_bluetoothAutoOnUnsupported_doNothing() { testScope.runTest { - bluetoothAutoOnInteractor.setEnabled(true) - - val actualValue by collectLastValue(bluetoothAutoOnInteractor.isEnabled) + whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false) + bluetoothAutoOnInteractor.setEnabled(true) runCurrent() - Truth.assertThat(actualValue).isEqualTo(false) + assertFalse(bluetoothAdapter.isAutoOnEnabled) } } @Test - fun testSet_bluetoothAutoOnSet_setNewValue() { + fun testSetEnabled_bluetoothAutoOnSupported_setNewValue() { testScope.runTest { - userRepository.setUserInfos(listOf(SYSTEM_USER)) - secureSettings.putIntForUser( - BluetoothAutoOnRepository.SETTING_NAME, - BluetoothAutoOnInteractor.DISABLED, - SYSTEM_USER_ID - ) - bluetoothAutoOnInteractor.setEnabled(true) - - val actualValue by collectLastValue(bluetoothAutoOnInteractor.isEnabled) + whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true) + bluetoothAutoOnInteractor.setEnabled(true) runCurrent() - Truth.assertThat(actualValue).isEqualTo(true) + assertTrue(bluetoothAdapter.isAutoOnEnabled) } } - - companion object { - private const val SYSTEM_USER_ID = 0 - private val SYSTEM_USER = - UserInfo(/* id= */ SYSTEM_USER_ID, /* name= */ "system user", /* flags= */ 0) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt index cd1452a6bf84..31192841ec77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt @@ -16,18 +16,14 @@ package com.android.systemui.qs.tiles.dialog.bluetooth -import android.content.pm.UserInfo -import android.os.UserHandle +import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.BluetoothEventManager +import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnInteractor.Companion.DISABLED -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnInteractor.Companion.ENABLED -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnRepository.Companion.SETTING_NAME -import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothAutoOnRepository.Companion.UNSET -import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -37,6 +33,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -46,83 +43,57 @@ class BluetoothAutoOnRepositoryTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) - private var secureSettings: FakeSettings = FakeSettings() - private val userRepository: FakeUserRepository = FakeUserRepository() + @Mock private lateinit var bluetoothAdapter: BluetoothAdapter + @Mock private lateinit var localBluetoothManager: LocalBluetoothManager + @Mock private lateinit var eventManager: BluetoothEventManager private lateinit var bluetoothAutoOnRepository: BluetoothAutoOnRepository @Before fun setUp() { + whenever(localBluetoothManager.eventManager).thenReturn(eventManager) bluetoothAutoOnRepository = BluetoothAutoOnRepository( - secureSettings, - userRepository, + localBluetoothManager, + bluetoothAdapter, testScope.backgroundScope, - testDispatcher + testDispatcher, ) - - userRepository.setUserInfos(listOf(SECONDARY_USER, SYSTEM_USER)) } @Test - fun testGetValue_valueUnset() { + fun testIsAutoOn_returnFalse() { testScope.runTest { - userRepository.setSelectedUserInfo(SYSTEM_USER) + whenever(bluetoothAdapter.isAutoOnEnabled).thenReturn(false) val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn) runCurrent() - assertThat(actualValue).isEqualTo(UNSET) - assertThat(bluetoothAutoOnRepository.isValuePresent()).isFalse() + assertThat(actualValue).isEqualTo(false) } } @Test - fun testGetValue_valueFalse() { + fun testIsAutoOn_returnTrue() { testScope.runTest { - userRepository.setSelectedUserInfo(SYSTEM_USER) + whenever(bluetoothAdapter.isAutoOnEnabled).thenReturn(true) val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn) - secureSettings.putIntForUser(SETTING_NAME, DISABLED, UserHandle.USER_SYSTEM) runCurrent() - assertThat(actualValue).isEqualTo(DISABLED) + assertThat(actualValue).isEqualTo(true) } } @Test - fun testGetValue_valueTrue() { + fun testIsAutoOnSupported_returnTrue() { testScope.runTest { - userRepository.setSelectedUserInfo(SYSTEM_USER) - val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn) + whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true) + val actualValue = bluetoothAutoOnRepository.isAutoOnSupported() - secureSettings.putIntForUser(SETTING_NAME, ENABLED, UserHandle.USER_SYSTEM) runCurrent() - assertThat(actualValue).isEqualTo(ENABLED) + assertThat(actualValue).isEqualTo(true) } } - - @Test - fun testGetValue_valueTrue_secondaryUser_returnTrue() { - testScope.runTest { - userRepository.setSelectedUserInfo(SECONDARY_USER) - val actualValue by collectLastValue(bluetoothAutoOnRepository.isAutoOn) - - secureSettings.putIntForUser(SETTING_NAME, DISABLED, SYSTEM_USER_ID) - secureSettings.putIntForUser(SETTING_NAME, ENABLED, SECONDARY_USER_ID) - runCurrent() - - assertThat(actualValue).isEqualTo(ENABLED) - } - } - - companion object { - private const val SYSTEM_USER_ID = 0 - private const val SECONDARY_USER_ID = 1 - private val SYSTEM_USER = - UserInfo(/* id= */ SYSTEM_USER_ID, /* name= */ "system user", /* flags= */ 0) - private val SECONDARY_USER = - UserInfo(/* id= */ SECONDARY_USER_ID, /* name= */ "secondary user", /* flags= */ 0) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt index 8ecb95334bc4..17b612714fe2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt @@ -109,7 +109,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { mBluetoothTileDialogDelegate = BluetoothTileDialogDelegate( - mContext, uiProperties, CONTENT_HEIGHT, ENABLED, @@ -119,14 +118,12 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { fakeSystemClock, uiEventLogger, logger, - sysuiDialogFactory, - LayoutInflater.from(mContext) + sysuiDialogFactory ) whenever( sysuiDialogFactory.create( - any(SystemUIDialog.Delegate::class.java), - any(Context::class.java) + any(SystemUIDialog.Delegate::class.java) ) ) .thenAnswer { @@ -216,7 +213,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false) val viewHolder = BluetoothTileDialogDelegate( - mContext, uiProperties, CONTENT_HEIGHT, ENABLED, @@ -227,7 +223,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, - LayoutInflater.from(mContext) ) .Adapter(bluetoothTileDialogCallback) .DeviceItemViewHolder(view) @@ -273,7 +268,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { val cachedHeight = Int.MAX_VALUE val dialog = BluetoothTileDialogDelegate( - mContext, BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED), cachedHeight, ENABLED, @@ -284,7 +278,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, - LayoutInflater.from(mContext) ) .createDialog() dialog.show() @@ -298,7 +291,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { testScope.runTest { val dialog = BluetoothTileDialogDelegate( - mContext, BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED), MATCH_PARENT, ENABLED, @@ -309,7 +301,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, - LayoutInflater.from(mContext) ) .createDialog() dialog.show() @@ -323,7 +314,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { testScope.runTest { val dialog = BluetoothTileDialogDelegate( - mContext, BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED), MATCH_PARENT, ENABLED, @@ -334,7 +324,6 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { uiEventLogger, logger, sysuiDialogFactory, - LayoutInflater.from(mContext) ) .createDialog() dialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index 39e2413be40e..c8a2aa64ffa2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.tiles.dialog.bluetooth -import android.content.pm.UserInfo +import android.bluetooth.BluetoothAdapter import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -26,19 +26,18 @@ import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.flags.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.kotlin.getMutableStateFlow import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher @@ -75,6 +74,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor + @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor + @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor @Mock private lateinit var activityStarter: ActivityStarter @@ -87,6 +88,10 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var bluetoothAdapter: BluetoothAdapter + + @Mock private lateinit var localBluetoothManager: LocalBluetoothManager + @Mock private lateinit var mBluetoothTileDialogDelegateDelegateFactory: BluetoothTileDialogDelegate.Factory @@ -100,8 +105,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { private lateinit var scheduler: TestCoroutineScheduler private lateinit var dispatcher: CoroutineDispatcher private lateinit var testScope: TestScope - private lateinit var secureSettings: FakeSettings - private lateinit var userRepository: FakeUserRepository @Before fun setUp() { @@ -109,14 +112,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { scheduler = TestCoroutineScheduler() dispatcher = UnconfinedTestDispatcher(scheduler) testScope = TestScope(dispatcher) - secureSettings = FakeSettings() - userRepository = FakeUserRepository() - userRepository.setUserInfos(listOf(SYSTEM_USER)) - secureSettings.putIntForUser( - BluetoothAutoOnRepository.SETTING_NAME, - BluetoothAutoOnInteractor.ENABLED, - SYSTEM_USER_ID - ) bluetoothTileDialogViewModel = BluetoothTileDialogViewModel( deviceItemInteractor, @@ -124,8 +119,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { // TODO(b/316822488): Create FakeBluetoothAutoOnInteractor. BluetoothAutoOnInteractor( BluetoothAutoOnRepository( - secureSettings, - userRepository, + localBluetoothManager, + bluetoothAdapter, testScope.backgroundScope, dispatcher ) @@ -148,7 +143,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { whenever( mBluetoothTileDialogDelegateDelegateFactory.create( any(), - any(), anyInt(), ArgumentMatchers.anyBoolean(), any(), @@ -157,6 +151,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { ) .thenReturn(bluetoothTileDialogDelegate) whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog) + whenever(sysuiDialog.context).thenReturn(mContext) whenever(bluetoothTileDialogDelegate.bluetoothStateToggle) .thenReturn(getMutableStateFlow(false)) whenever(bluetoothTileDialogDelegate.deviceItemClick) @@ -169,7 +164,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Test fun testShowDialog_noAnimation() { testScope.runTest { - bluetoothTileDialogViewModel.showDialog(context, null) + bluetoothTileDialogViewModel.showDialog(null) verify(mDialogTransitionAnimator, never()).showFromView(any(), any(), any(), any()) } @@ -178,7 +173,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Test fun testShowDialog_animated() { testScope.runTest { - bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext)) + bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext)) verify(mDialogTransitionAnimator).showFromView(any(), any(), nullable(), anyBoolean()) } @@ -188,7 +183,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { fun testShowDialog_animated_callInBackgroundThread() { testScope.runTest { backgroundExecutor.execute { - bluetoothTileDialogViewModel.showDialog(mContext, LinearLayout(mContext)) + bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext)) verify(mDialogTransitionAnimator) .showFromView(any(), any(), nullable(), anyBoolean()) @@ -199,7 +194,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Test fun testShowDialog_fetchDeviceItem() { testScope.runTest { - bluetoothTileDialogViewModel.showDialog(context, null) + bluetoothTileDialogViewModel.showDialog(null) verify(deviceItemInteractor).deviceItemUpdate } @@ -208,7 +203,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Test fun testShowDialog_withBluetoothStateValue() { testScope.runTest { - bluetoothTileDialogViewModel.showDialog(context, null) + bluetoothTileDialogViewModel.showDialog(null) verify(bluetoothStateInteractor).bluetoothStateUpdate } @@ -218,7 +213,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { fun testStartSettingsActivity_activityLaunched_dialogDismissed() { testScope.runTest { whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice) - bluetoothTileDialogViewModel.showDialog(context, null) + bluetoothTileDialogViewModel.showDialog(null) val clickedView = View(context) bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView) @@ -265,26 +260,22 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { } @Test - fun testIsAutoOnToggleFeatureAvailable_flagOn_settingValueSet_returnTrue() { + fun testIsAutoOnToggleFeatureAvailable_returnTrue() { testScope.runTest { + whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true) + val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable() assertThat(actual).isTrue() } } @Test - fun testIsAutoOnToggleFeatureAvailable_flagOff_settingValueSet_returnFalse() { + fun testIsAutoOnToggleFeatureAvailable_returnFalse() { testScope.runTest { - mSetFlagsRule.disableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE) + whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false) val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable() assertThat(actual).isFalse() } } - - companion object { - private const val SYSTEM_USER_ID = 0 - private val SYSTEM_USER = - UserInfo(/* id= */ SYSTEM_USER_ID, /* name= */ "system user", /* flags= */ 0) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index ee2eb806341f..0e89d8072a2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -845,7 +845,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT) public void imageResolver_differentNotificationUser_createsUserContext() throws Exception { UserHandle user = new UserHandle(33); Context userContext = new SysuiTestableContext(mContext); 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 65491937c285..fe0d9d06c8f4 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 @@ -23,6 +23,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; +import static com.android.systemui.concurrency.FakeExecutorKosmosKt.getFakeExecutor; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; import static junit.framework.Assert.assertNotNull; @@ -124,12 +125,11 @@ public class NotificationGutsManagerTest extends SysuiTestCase { private NotificationChannel mTestNotificationChannel = new NotificationChannel( TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); - private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); - private TestScope mTestScope = mKosmos.getTestScope(); - private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); - private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); - private TestableLooper mTestableLooper; - private Handler mHandler; + private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); + private final TestScope mTestScope = mKosmos.getTestScope(); + private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope()); + private final FakeExecutor mExecutor = mKosmos.getFakeExecutor(); + private final Handler mHandler = mKosmos.getFakeExecutorHandler(); private NotificationTestHelper mHelper; private NotificationGutsManager mGutsManager; @@ -171,10 +171,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase { @Before public void setUp() { - mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); - mHandler = Handler.createAsync(mTestableLooper.getLooper()); - mHelper = new NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)); + mHelper = new NotificationTestHelper(mContext, mDependency); when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mWindowRootViewVisibilityInteractor = new WindowRootViewVisibilityInteractor( @@ -248,7 +246,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem)); assertEquals(View.INVISIBLE, guts.getVisibility()); - mTestableLooper.processAllMessages(); + mExecutor.runAllReady(); verify(guts).openControls( anyInt(), anyInt(), @@ -261,7 +259,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); verify(row, times(1)).setGutsView(any()); - mTestableLooper.processAllMessages(); + mExecutor.runAllReady(); verify(mHeadsUpManager).setGutsShown(realRow.getEntry(), false); } @@ -352,7 +350,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { when(entry.getGuts()).thenReturn(guts); assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem)); - mTestableLooper.processAllMessages(); + mExecutor.runAllReady(); verify(guts).openControls( anyInt(), anyInt(), @@ -365,7 +363,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase { row.onDensityOrFontScaleChanged(); mGutsManager.onDensityOrFontScaleChanged(entry); - mTestableLooper.processAllMessages(); + mExecutor.runAllReady(); mGutsManager.closeAndSaveGuts(false, false, false, 0, 0, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 012ff2e31562..65a960b5ff6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -27,12 +27,11 @@ import android.content.pm.ShortcutManager import android.content.pm.launcherApps import android.graphics.Color import android.os.Binder -import android.os.Handler +import android.os.fakeExecutorHandler import android.os.userManager import android.provider.Settings import android.service.notification.NotificationListenerService.Ranking import android.testing.AndroidTestingRunner -import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.util.ArraySet import android.view.View @@ -45,6 +44,7 @@ import com.android.internal.logging.metricsLogger import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.statusbar.statusBarService import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.people.widget.PeopleSpaceWidgetManager @@ -71,9 +71,7 @@ import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.statusbar.policy.deviceProvisionedController import com.android.systemui.statusbar.policy.headsUpManager import com.android.systemui.testKosmos -import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.kotlin.JavaAdapter -import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.wmshell.BubblesManager import java.util.Optional import junit.framework.Assert @@ -106,9 +104,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val javaAdapter = JavaAdapter(testScope.backgroundScope) - private val executor = FakeExecutor(FakeSystemClock()) - private lateinit var testableLooper: TestableLooper - private lateinit var handler: Handler + private val executor = kosmos.fakeExecutor + private val handler = kosmos.fakeExecutorHandler private lateinit var helper: NotificationTestHelper private lateinit var gutsManager: NotificationGutsManager private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor @@ -148,10 +145,8 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val sceneContainerFlags = kosmos.fakeSceneContainerFlags sceneContainerFlags.enabled = true - testableLooper = TestableLooper.get(this) allowTestableLooperAsMainThread() - handler = Handler.createAsync(testableLooper.getLooper()) - helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + helper = NotificationTestHelper(mContext, mDependency) Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false) windowRootViewVisibilityInteractor = WindowRootViewVisibilityInteractor( @@ -227,7 +222,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { Mockito.`when`(row.guts).thenReturn(guts) Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong()) - testableLooper.processAllMessages() + executor.runAllReady() verify(guts) .openControls( ArgumentMatchers.anyInt(), @@ -247,7 +242,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { ArgumentMatchers.anyBoolean() ) verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any()) - testableLooper.processAllMessages() + executor.runAllReady() verify(headsUpManager).setGutsShown(realRow.entry, false) } @@ -343,7 +338,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { Mockito.`when`(entry.row).thenReturn(row) Mockito.`when`(entry.getGuts()).thenReturn(guts) Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) - testableLooper.processAllMessages() + executor.runAllReady() verify(guts) .openControls( ArgumentMatchers.anyInt(), @@ -356,7 +351,7 @@ class NotificationGutsManagerWithScenesTest : SysuiTestCase() { verify(row).setGutsView(ArgumentMatchers.any()) row.onDensityOrFontScaleChanged() gutsManager.onDensityOrFontScaleChanged(entry) - testableLooper.processAllMessages() + executor.runAllReady() gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) verify(guts) .closeControls( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index 09a3eb480a49..954335efd33a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -615,10 +615,8 @@ public class NotificationTestHelper { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( Context.LAYOUT_INFLATER_SERVICE); - if (com.android.systemui.Flags.notificationRowUserContext()) { - inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock, - mRowInflaterTaskLogger)); - } + inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock, + mRowInflaterTaskLogger)); mRow = (ExpandableNotificationRow) inflater.inflate( R.layout.status_bar_notification_row, null /* root */, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java index 8f88501a38f7..a15b4cd37184 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java @@ -25,8 +25,6 @@ import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.View; @@ -44,7 +42,6 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @SmallTest -@RunWithLooper public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase { private View mView; @@ -53,11 +50,7 @@ public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase @Before public void setup() throws Exception { - allowTestableLooperAsMainThread(); - NotificationTestHelper helper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); + NotificationTestHelper helper = new NotificationTestHelper(mContext, mDependency); mView = LayoutInflater.from(mContext).inflate( com.android.internal.R.layout.notification_template_material_big_picture, null); mRow = helper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt index 3fa68bb69da2..fe2971c46c32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.testing.TestableLooper.RunWithLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.R @@ -41,7 +39,6 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) -@RunWithLooper class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow @@ -49,8 +46,7 @@ class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() { @Before fun setUp() { - allowTestableLooperAsMainThread() - helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + helper = NotificationTestHelper(mContext, mDependency) mRow = helper.createRow() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index 45f7c5a6fdc0..2d72c7e0b714 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -17,8 +17,6 @@ package com.android.systemui.statusbar.notification.row.wrapper; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.RemoteViews; @@ -36,18 +34,13 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) -@RunWithLooper public class NotificationCustomViewWrapperTest extends SysuiTestCase { private ExpandableNotificationRow mRow; @Before public void setUp() throws Exception { - allowTestableLooperAsMainThread(); - NotificationTestHelper helper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); + NotificationTestHelper helper = new NotificationTestHelper(mContext, mDependency); mRow = helper.createRow(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt index c0444b563a2c..f26c18b1d197 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt @@ -18,8 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.testing.TestableLooper.RunWithLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.widget.MessagingGroup @@ -39,7 +37,6 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) -@RunWithLooper class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow @@ -47,8 +44,7 @@ class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() { @Before fun setUp() { - allowTestableLooperAsMainThread() - helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + helper = NotificationTestHelper(mContext, mDependency) mRow = helper.createRow() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index f7632aa37d4b..54eed26adaf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -69,7 +69,7 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { TestUiOffloadThread(looper.looper) ) - helper = NotificationTestHelper(mContext, mDependency, looper) + helper = NotificationTestHelper(mContext, mDependency) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around // it. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index 93a9e597ca90..e3a77d32b90f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -39,7 +39,6 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @SmallTest -@RunWithLooper public class NotificationViewWrapperTest extends SysuiTestCase { private View mView; @@ -48,13 +47,9 @@ public class NotificationViewWrapperTest extends SysuiTestCase { @Before public void setup() throws Exception { - allowTestableLooperAsMainThread(); mView = mock(View.class); when(mView.getContext()).thenReturn(mContext); - NotificationTestHelper helper = new NotificationTestHelper( - mContext, - mDependency, - TestableLooper.get(this)); + NotificationTestHelper helper = new NotificationTestHelper(mContext, mDependency); mRow = helper.createRow(); mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 138e1fa5c29c..c308a987455b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -130,35 +130,35 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test - fun testShouldShowEmptyShadeView_trueWhenNoNotifs() = + fun testShouldIncludeEmptyShadeView_trueWhenNoNotifs() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) runCurrent() // THEN empty shade is visible - assertThat(shouldShow).isTrue() + assertThat(shouldInclude).isTrue() } @Test - fun testShouldShowEmptyShadeView_falseWhenNotifs() = + fun testShouldIncludeEmptyShadeView_falseWhenNotifs() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) runCurrent() // THEN empty shade is not visible - assertThat(shouldShow).isFalse() + assertThat(shouldInclude).isFalse() } @Test - fun testShouldShowEmptyShadeView_falseWhenQsExpandedDefault() = + fun testShouldIncludeEmptyShadeView_falseWhenQsExpandedDefault() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -167,13 +167,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible - assertThat(shouldShow).isFalse() + assertThat(shouldInclude).isFalse() } @Test - fun testShouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() = + fun testShouldIncludeEmptyShadeView_trueWhenQsExpandedInSplitShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -185,13 +185,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible - assertThat(shouldShow).isTrue() + assertThat(shouldInclude).isTrue() } @Test - fun testShouldShowEmptyShadeView_trueWhenLockedShade() = + fun testShouldIncludeEmptyShadeView_trueWhenLockedShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -200,13 +200,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is visible - assertThat(shouldShow).isTrue() + assertThat(shouldInclude).isTrue() } @Test - fun testShouldShowEmptyShadeView_falseWhenKeyguard() = + fun testShouldIncludeEmptyShadeView_falseWhenKeyguard() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -215,13 +215,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible - assertThat(shouldShow).isFalse() + assertThat(shouldInclude).isFalse() } @Test - fun testShouldShowEmptyShadeView_falseWhenStartingToSleep() = + fun testShouldIncludeEmptyShadeView_falseWhenStartingToSleep() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowEmptyShadeView) + val shouldInclude by collectLastValue(underTest.shouldShowEmptyShadeView) // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) @@ -232,7 +232,7 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN empty shade is not visible - assertThat(shouldShow).isFalse() + assertThat(shouldInclude).isFalse() } @Test @@ -282,9 +282,9 @@ class NotificationListViewModelTest : SysuiTestCase() { } @Test - fun testShouldShowFooterView_trueWhenShade() = + fun testShouldIncludeFooterView_trueWhenShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -294,13 +294,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible - assertThat(shouldShow?.value).isTrue() + assertThat(shouldInclude?.value).isTrue() } @Test - fun testShouldShowFooterView_trueWhenLockedShade() = + fun testShouldIncludeFooterView_trueWhenLockedShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -310,13 +310,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible - assertThat(shouldShow?.value).isTrue() + assertThat(shouldInclude?.value).isTrue() } @Test - fun testShouldShowFooterView_falseWhenKeyguard() = + fun testShouldIncludeFooterView_falseWhenKeyguard() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -325,13 +325,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + assertThat(shouldInclude?.value).isFalse() } @Test - fun testShouldShowFooterView_falseWhenUserNotSetUp() = + fun testShouldIncludeFooterView_falseWhenUserNotSetUp() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -343,13 +343,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + assertThat(shouldInclude?.value).isFalse() } @Test - fun testShouldShowFooterView_falseWhenStartingToSleep() = + fun testShouldIncludeFooterView_falseWhenStartingToSleep() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -361,13 +361,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + assertThat(shouldInclude?.value).isFalse() } @Test - fun testShouldShowFooterView_falseWhenQsExpandedDefault() = + fun testShouldIncludeFooterView_falseWhenQsExpandedDefault() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -380,13 +380,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + assertThat(shouldInclude?.value).isFalse() } @Test - fun testShouldShowFooterView_trueWhenQsExpandedSplitShade() = + fun testShouldIncludeFooterView_trueWhenQsExpandedSplitShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -401,13 +401,13 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is visible - assertThat(shouldShow?.value).isTrue() + assertThat(shouldInclude?.value).isTrue() } @Test - fun testShouldShowFooterView_falseWhenRemoteInputActive() = + fun testShouldIncludeFooterView_falseWhenRemoteInputActive() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) @@ -419,55 +419,67 @@ class NotificationListViewModelTest : SysuiTestCase() { runCurrent() // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + assertThat(shouldInclude?.value).isFalse() } @Test - fun testShouldShowFooterView_falseWhenShadeIsClosed() = + fun testShouldIncludeFooterView_animatesWhenShade() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) - // AND shade is closed + // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(0f) + fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() - // THEN footer is not visible - assertThat(shouldShow?.value).isFalse() + // THEN footer visibility animates + assertThat(shouldInclude?.isAnimating).isTrue() } @Test - fun testShouldShowFooterView_animatesWhenShade() = + fun testShouldIncludeFooterView_notAnimatingOnKeyguard() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView) // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) - // AND shade is open and fully expanded - fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + // AND we are on the keyguard + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() - // THEN footer visibility animates - assertThat(shouldShow?.isAnimating).isTrue() + // THEN footer visibility does not animate + assertThat(shouldInclude?.isAnimating).isFalse() } @Test - fun testShouldShowFooterView_notAnimatingOnKeyguard() = + fun testShouldHideFooterView_trueWhenShadeIsClosed() = testScope.runTest { - val shouldShow by collectLastValue(underTest.shouldShowFooterView) + val shouldHide by collectLastValue(underTest.shouldHideFooterView) - // WHEN has notifs - activeNotificationListRepository.setActiveNotifs(count = 2) - // AND we are on the keyguard - fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + // WHEN shade is closed + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) + fakeShadeRepository.setLegacyShadeExpansion(0f) + runCurrent() + + // THEN footer is hidden + assertThat(shouldHide).isTrue() + } + + @Test + fun testShouldHideFooterView_falseWhenShadeIsOpen() = + testScope.runTest { + val shouldHide by collectLastValue(underTest.shouldHideFooterView) + + // WHEN shade is open + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) fakeShadeRepository.setLegacyShadeExpansion(1f) runCurrent() - // THEN footer visibility does not animate - assertThat(shouldShow?.isAnimating).isFalse() + // THEN footer is hidden + assertThat(shouldHide).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 98556514f8ec..f761bcfe63d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -868,6 +868,24 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + fun networkName_usingEagerStrategy_retainsNameBetweenSubscribers() = + testScope.runTest { + // Use the [StateFlow.value] getter so we can prove that the collection happens + // even when there is no [Job] + + // Starts out default + assertThat(underTest.networkName.value).isEqualTo(DEFAULT_NAME_MODEL) + + val intent = spnIntent() + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + captor.value!!.onReceive(context, intent) + + // The value is still there despite no active subscribers + assertThat(underTest.networkName.value).isEqualTo(intent.toNetworkNameModel(SEP)) + } + + @Test fun operatorAlphaShort_tracked() = testScope.runTest { var latest: String? = null 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 index e861892252fa..c879588a1ab7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -19,6 +19,7 @@ package com.android.systemui.kosmos import android.content.applicationContext +import android.os.fakeExecutorHandler import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.bouncerRepository import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor @@ -27,6 +28,7 @@ import com.android.systemui.common.ui.data.repository.fakeConfigurationRepositor 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.concurrency.fakeExecutor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -65,6 +67,8 @@ class KosmosJavaAdapter( val testScope by lazy { kosmos.testScope } val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic } val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags } + val fakeExecutor by lazy { kosmos.fakeExecutor } + val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler } val configurationRepository by lazy { kosmos.fakeConfigurationRepository } val configurationInteractor by lazy { kosmos.configurationInteractor } val bouncerRepository by lazy { kosmos.bouncerRepository } diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig index 532db126bff2..c130ceef1e08 100644 --- a/services/autofill/features.aconfig +++ b/services/autofill/features.aconfig @@ -16,6 +16,7 @@ flag { flag { name: "autofill_credman_dev_integration" + is_exported: true namespace: "autofill" description: "Guards against Autofill-Credman Phase1 developer integration via new APIs" bug: "320730001" diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 53c0f58d0067..0f9517460ee9 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -12360,7 +12360,8 @@ public class AudioService extends IAudioService.Stub } private boolean callerHasPermission(String permission) { - return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; } /** @return true if projection is a valid MediaProjection that can project audio. */ diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 04e7f77615a6..851d1978dd98 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -894,6 +894,7 @@ public class DisplayDeviceConfig { @Nullable private HdrBrightnessData mHdrBrightnessData; + // Null if low brightness mode is disabled - in config or by flag. @Nullable public LowBrightnessData mLowBrightnessData; @@ -1063,6 +1064,9 @@ public class DisplayDeviceConfig { * @return The brightness mapping nits array. */ public float[] getNits() { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mNits; + } return mNits; } @@ -1071,7 +1075,11 @@ public class DisplayDeviceConfig { * * @return The backlight mapping value array. */ + @VisibleForTesting public float[] getBacklight() { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mBacklight; + } return mBacklight; } @@ -1083,9 +1091,26 @@ public class DisplayDeviceConfig { * @return backlight value on the HAL scale of 0-1 */ public float getBacklightFromBrightness(float brightness) { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mBrightnessToBacklight.interpolate(brightness); + } return mBrightnessToBacklightSpline.interpolate(brightness); } + private float getBrightnessFromBacklight(float brightness) { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mBacklightToBrightness.interpolate(brightness); + } + return mBacklightToBrightnessSpline.interpolate(brightness); + } + + private Spline getBacklightToBrightnessSpline() { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mBacklightToBrightness; + } + return mBacklightToBrightnessSpline; + } + /** * Calculates the nits value for the specified backlight value if a mapping exists. * @@ -1093,6 +1118,14 @@ public class DisplayDeviceConfig { * exits. */ public float getNitsFromBacklight(float backlight) { + if (mLowBrightnessData != null) { + if (mLowBrightnessData.mBacklightToNits == null) { + return INVALID_NITS; + } + backlight = Math.max(backlight, mBacklightMinimum); + return mLowBrightnessData.mBacklightToNits.interpolate(backlight); + } + if (mBacklightToNitsSpline == null) { return INVALID_NITS; } @@ -1100,6 +1133,20 @@ public class DisplayDeviceConfig { return mBacklightToNitsSpline.interpolate(backlight); } + private float getBacklightFromNits(float nits) { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mNitsToBacklight.interpolate(nits); + } + return mNitsToBacklightSpline.interpolate(nits); + } + + private Spline getNitsToBacklightSpline() { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mNitsToBacklight; + } + return mNitsToBacklightSpline; + } + /** * @return true if there is sdrHdrRatioMap, false otherwise. */ @@ -1126,13 +1173,13 @@ public class DisplayDeviceConfig { float ratio = Math.min(mSdrToHdrRatioSpline.interpolate(nits), maxDesiredHdrSdrRatio); float hdrNits = nits * ratio; - if (mNitsToBacklightSpline == null) { + if (getNitsToBacklightSpline() == null) { return PowerManager.BRIGHTNESS_INVALID; } - float hdrBacklight = mNitsToBacklightSpline.interpolate(hdrNits); + float hdrBacklight = getBacklightFromNits(hdrNits); hdrBacklight = Math.max(mBacklightMinimum, Math.min(mBacklightMaximum, hdrBacklight)); - float hdrBrightness = mBacklightToBrightnessSpline.interpolate(hdrBacklight); + float hdrBrightness = getBrightnessFromBacklight(hdrBacklight); if (DEBUG) { Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness @@ -1154,6 +1201,9 @@ public class DisplayDeviceConfig { * @return brightness array */ public float[] getBrightness() { + if (mLowBrightnessData != null) { + return mLowBrightnessData.mBrightness; + } return mBrightness; } @@ -1987,7 +2037,8 @@ public class DisplayDeviceConfig { + "mHdrBrightnessData= " + mHdrBrightnessData + "\n" + "mBrightnessCapForWearBedtimeMode= " + mBrightnessCapForWearBedtimeMode + "\n" - + (mLowBrightnessData != null ? mLowBrightnessData.toString() : "") + + "mLowBrightnessData:" + (mLowBrightnessData != null + ? mLowBrightnessData.toString() : "null") + "}"; } @@ -2588,9 +2639,9 @@ public class DisplayDeviceConfig { // A negative value means that there's no threshold mLowDisplayBrightnessThresholds[i] = thresholdNits; } else { - float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits); + float thresholdBacklight = getBacklightFromNits(thresholdNits); mLowDisplayBrightnessThresholds[i] = - mBacklightToBrightnessSpline.interpolate(thresholdBacklight); + getBrightnessFromBacklight(thresholdBacklight); } mLowAmbientBrightnessThresholds[i] = lowerThresholdDisplayBrightnessPoints @@ -2639,9 +2690,9 @@ public class DisplayDeviceConfig { // A negative value means that there's no threshold mHighDisplayBrightnessThresholds[i] = thresholdNits; } else { - float thresholdBacklight = mNitsToBacklightSpline.interpolate(thresholdNits); + float thresholdBacklight = getBacklightFromNits(thresholdNits); mHighDisplayBrightnessThresholds[i] = - mBacklightToBrightnessSpline.interpolate(thresholdBacklight); + getBrightnessFromBacklight(thresholdBacklight); } mHighAmbientBrightnessThresholds[i] = higherThresholdDisplayBrightnessPoints @@ -2658,7 +2709,7 @@ public class DisplayDeviceConfig { loadAutoBrightnessBrighteningLightDebounceIdle(autoBrightness); loadAutoBrightnessDarkeningLightDebounceIdle(autoBrightness); mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags, - autoBrightness, mBacklightToBrightnessSpline); + autoBrightness, getBacklightToBrightnessSpline()); loadEnableAutoBrightness(autoBrightness); } @@ -2832,17 +2883,10 @@ public class DisplayDeviceConfig { // These splines are used to convert from the system brightness value to the HAL backlight // value private void createBacklightConversionSplines() { - if (mLowBrightnessData != null) { - mBrightnessToBacklightSpline = mLowBrightnessData.mBrightnessToBacklight; - mBacklightToBrightnessSpline = mLowBrightnessData.mBacklightToBrightness; - mBacklightToNitsSpline = mLowBrightnessData.mBacklightToNits; - mNitsToBacklightSpline = mLowBrightnessData.mNitsToBacklight; - - mNits = mLowBrightnessData.mNits; - mBrightness = mLowBrightnessData.mBrightness; - mBacklight = mLowBrightnessData.mBacklight; - return; - } + + + // Create original brightness splines - not using low brightness mode arrays - this is + // so that we can continue to log the original brightness splines. mBrightness = new float[mBacklight.length]; for (int i = 0; i < mBrightness.length; i++) { @@ -2884,7 +2928,7 @@ public class DisplayDeviceConfig { + mBacklightMaximum); } mHbmData.transitionPoint = - mBacklightToBrightnessSpline.interpolate(transitionPointBacklightScale); + getBrightnessFromBacklight(transitionPointBacklightScale); final HbmTiming hbmTiming = hbm.getTiming_all(); mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000; mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000; @@ -2953,7 +2997,7 @@ public class DisplayDeviceConfig { continue; } luxToTransitionPointMap.put(lux, - mBacklightToBrightnessSpline.interpolate(maxBrightness)); + getBrightnessFromBacklight(maxBrightness)); } if (!luxToTransitionPointMap.isEmpty()) { mLuxThrottlingData.put(mappedType, luxToTransitionPointMap); @@ -3048,7 +3092,7 @@ public class DisplayDeviceConfig { private void loadAutoBrightnessConfigsFromConfigXml() { mDisplayBrightnessMapping = new DisplayBrightnessMappingConfig(mContext, mFlags, - /* autoBrightnessConfig= */ null, mBacklightToBrightnessSpline); + /* autoBrightnessConfig= */ null, getBacklightToBrightnessSpline()); } private void loadBrightnessChangeThresholdsFromXml() { diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig index fee4bf377ddc..14e964b26c6b 100644 --- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig +++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig @@ -2,6 +2,7 @@ package: "com.android.server.feature.flags" flag{ name: "enable_read_dropbox_permission" + is_exported: true namespace: "preload_safety" description: "Feature flag for permission to Read dropbox data" bug: "287512663" diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index b2e01c5f23f2..c42cceab55be 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -2,6 +2,7 @@ package: "com.android.server.power.optimization" flag { name: "power_monitor_api" + is_exported: true namespace: "backstage_power" description: "Feature flag for ODPM API" bug: "295027807" diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig index 1411acc4ab84..2afbcd6f101d 100644 --- a/services/core/java/com/android/server/webkit/flags.aconfig +++ b/services/core/java/com/android/server/webkit/flags.aconfig @@ -2,6 +2,7 @@ package: "android.webkit" flag { name: "update_service_v2" + is_exported: true namespace: "webview" description: "Using a new version of the WebView update service" bug: "308907090" diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java index 5460e4e87e2f..64dbe719311a 100644 --- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java +++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java @@ -43,6 +43,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -392,6 +393,7 @@ public class AttachedChoreographerTest { } @Test + @Ignore("Can be enabled only after b/330536267 is ready") public void testChoreographerDivisorRefreshRate() { for (int divisor : new int[]{2, 3}) { CountDownLatch continueLatch = new CountDownLatch(1); @@ -420,6 +422,7 @@ public class AttachedChoreographerTest { } @Test + @Ignore("Can be enabled only after b/330536267 is ready") public void testChoreographerAttachedAfterSetFrameRate() { Log.i(TAG, "starting testChoreographerAttachedAfterSetFrameRate"); diff --git a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java index df003b6aeab2..fb7a6ab42d95 100644 --- a/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java +++ b/tools/app_metadata_bundles/src/aslgen/java/com/android/aslgen/Main.java @@ -18,6 +18,7 @@ package com.android.aslgen; import com.android.asllib.AndroidSafetyLabel; import com.android.asllib.AndroidSafetyLabel.Format; +import com.android.asllib.util.MalformedXmlException; import org.xml.sax.SAXException; @@ -32,7 +33,11 @@ public class Main { /** Takes the options to make file conversion. */ public static void main(String[] args) - throws IOException, ParserConfigurationException, SAXException, TransformerException { + throws IOException, + ParserConfigurationException, + SAXException, + TransformerException, + MalformedXmlException { String inFile = null; String outFile = null; diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java index 0f7ce6894063..bc8063ef7b5f 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -53,7 +55,7 @@ public class AndroidSafetyLabel implements AslMarshallable { /** Reads a {@link AndroidSafetyLabel} from an {@link InputStream}. */ // TODO(b/329902686): Support parsing from on-device. public static AndroidSafetyLabel readFromStream(InputStream in, Format format) - throws IOException, ParserConfigurationException, SAXException { + throws IOException, ParserConfigurationException, SAXException, MalformedXmlException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); Document document = factory.newDocumentBuilder().parse(in); @@ -65,9 +67,9 @@ public class AndroidSafetyLabel implements AslMarshallable { return new AndroidSafetyLabelFactory() .createFromHrElements( - XmlUtils.asElementList( - document.getElementsByTagName( - XmlUtils.HR_TAG_APP_METADATA_BUNDLES))); + List.of( + XmlUtils.getSingleElement( + document, XmlUtils.HR_TAG_APP_METADATA_BUNDLES))); case ON_DEVICE: throw new IllegalArgumentException( "Parsing from on-device format is not supported at this time."); diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java index 9b0f05b0c633..7e7fcf9c08ba 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Element; import java.util.List; @@ -24,7 +26,8 @@ public class AndroidSafetyLabelFactory implements AslMarshallableFactory<Android /** Creates an {@link AndroidSafetyLabel} from human-readable DOM element */ @Override - public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles) { + public AndroidSafetyLabel createFromHrElements(List<Element> appMetadataBundles) + throws MalformedXmlException { Element appMetadataBundlesEle = XmlUtils.getSingleElement(appMetadataBundles); Element safetyLabelsEle = XmlUtils.getSingleChildElement( diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java index b607353791ff..b8f9f0ef6235 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Element; import java.util.List; @@ -23,5 +25,5 @@ import java.util.List; public interface AslMarshallableFactory<T extends AslMarshallable> { /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */ - T createFromHrElements(List<Element> elements); + T createFromHrElements(List<Element> elements) throws MalformedXmlException; } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java index 5a52591eaf8c..d9463452d7bc 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Element; import java.util.HashMap; @@ -24,12 +26,16 @@ import java.util.Map; public class DataCategoryFactory implements AslMarshallableFactory<DataCategory> { @Override - public DataCategory createFromHrElements(List<Element> elements) { + public DataCategory createFromHrElements(List<Element> elements) throws MalformedXmlException { String categoryName = null; Map<String, DataType> dataTypeMap = new HashMap<String, DataType>(); for (Element ele : elements) { categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE); + if (!DataTypeConstants.getValidDataTypes().contains(dataTypeName)) { + throw new MalformedXmlException( + String.format("Unrecognized data type name: %s", dataTypeName)); + } dataTypeMap.put(dataTypeName, new DataTypeFactory().createFromHrElements(List.of(ele))); } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java index c758ab923bbf..1adb140f446d 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -29,7 +31,7 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { /** Creates a {@link DataLabels} from the human-readable DOM element. */ @Override - public DataLabels createFromHrElements(List<Element> elements) { + public DataLabels createFromHrElements(List<Element> elements) throws MalformedXmlException { Element ele = XmlUtils.getSingleElement(elements); Map<String, DataCategory> dataAccessed = getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_ACCESSED); @@ -37,13 +39,54 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_COLLECTED); Map<String, DataCategory> dataShared = getDataCategoriesWithTag(ele, XmlUtils.HR_TAG_DATA_SHARED); + + // Validate booleans such as isCollectionOptional, isSharingOptional. + for (DataCategory dataCategory : dataAccessed.values()) { + for (DataType dataType : dataCategory.getDataTypes().values()) { + if (dataType.getIsSharingOptional() != null) { + throw new MalformedXmlException( + String.format( + "isSharingOptional was unexpectedly defined on a DataType" + + " belonging to data accessed: %s", + dataType.getDataTypeName())); + } + if (dataType.getIsCollectionOptional() != null) { + throw new MalformedXmlException( + String.format( + "isCollectionOptional was unexpectedly defined on a DataType" + + " belonging to data accessed: %s", + dataType.getDataTypeName())); + } + } + } + for (DataCategory dataCategory : dataCollected.values()) { + for (DataType dataType : dataCategory.getDataTypes().values()) { + if (dataType.getIsSharingOptional() != null) { + throw new MalformedXmlException( + String.format( + "isSharingOptional was unexpectedly defined on a DataType" + + " belonging to data collected: %s", + dataType.getDataTypeName())); + } + } + } + for (DataCategory dataCategory : dataShared.values()) { + for (DataType dataType : dataCategory.getDataTypes().values()) { + if (dataType.getIsCollectionOptional() != null) { + throw new MalformedXmlException( + String.format( + "isCollectionOptional was unexpectedly defined on a DataType" + + " belonging to data shared: %s", + dataType.getDataTypeName())); + } + } + } + return new DataLabels(dataAccessed, dataCollected, dataShared); } private static Map<String, DataCategory> getDataCategoriesWithTag( - Element dataLabelsEle, String dataCategoryUsageTypeTag) { - Map<String, Map<String, DataType>> dataTypeMap = - new HashMap<String, Map<String, DataType>>(); + Element dataLabelsEle, String dataCategoryUsageTypeTag) throws MalformedXmlException { NodeList dataUsedNodeList = dataLabelsEle.getElementsByTagName(dataCategoryUsageTypeTag); Map<String, DataCategory> dataCategoryMap = new HashMap<String, DataCategory>(); @@ -51,6 +94,10 @@ public class DataLabelsFactory implements AslMarshallableFactory<DataLabels> { for (int i = 0; i < dataUsedNodeList.getLength(); i++) { Element dataUsedEle = (Element) dataUsedNodeList.item(i); String dataCategoryName = dataUsedEle.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY); + if (!DataCategoryConstants.getValidDataCategories().contains(dataCategoryName)) { + throw new MalformedXmlException( + String.format("Unrecognized category name: %s", dataCategoryName)); + } dataCategoryNames.add(dataCategoryName); } for (String dataCategoryName : dataCategoryNames) { diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java index 99f8a8b1b152..e3d1587d860c 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java @@ -35,10 +35,10 @@ public class DataTypeFactory implements AslMarshallableFactory<DataType> { .collect(Collectors.toUnmodifiableSet()); Boolean isCollectionOptional = XmlUtils.fromString( - hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL)); + hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL)); Boolean isSharingOptional = XmlUtils.fromString( - hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL)); + hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL)); Boolean ephemeral = XmlUtils.fromString(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_EPHEMERAL)); return new DataType( diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java index 68e83fe30db1..80b9f5783b9d 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Element; import java.util.List; @@ -24,7 +26,7 @@ public class SafetyLabelsFactory implements AslMarshallableFactory<SafetyLabels> /** Creates a {@link SafetyLabels} from the human-readable DOM element. */ @Override - public SafetyLabels createFromHrElements(List<Element> elements) { + public SafetyLabels createFromHrElements(List<Element> elements) throws MalformedXmlException { Element safetyLabelsEle = XmlUtils.getSingleElement(elements); Long version; try { diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java index 3c89a308036f..3bc9ccc2138b 100644 --- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java @@ -16,6 +16,8 @@ package com.android.asllib; +import com.android.asllib.util.MalformedXmlException; + import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -61,30 +63,34 @@ public class XmlUtils { public static final String FALSE_STR = "false"; /** Gets the single top-level {@link Element} having the {@param tagName}. */ - public static Element getSingleElement(Document doc, String tagName) { + public static Element getSingleElement(Document doc, String tagName) + throws MalformedXmlException { var elements = doc.getElementsByTagName(tagName); - return getSingleElement(elements); + return getSingleElement(elements, tagName); } /** * Gets the single {@link Element} within {@param parentEle} and having the {@param tagName}. */ - public static Element getSingleChildElement(Element parentEle, String tagName) { + public static Element getSingleChildElement(Element parentEle, String tagName) + throws MalformedXmlException { var elements = parentEle.getElementsByTagName(tagName); - return getSingleElement(elements); + return getSingleElement(elements, tagName); } /** Gets the single {@link Element} from {@param elements} */ - public static Element getSingleElement(NodeList elements) { + public static Element getSingleElement(NodeList elements, String tagName) + throws MalformedXmlException { if (elements.getLength() != 1) { - throw new IllegalArgumentException( + throw new MalformedXmlException( String.format( - "Expected 1 element in NodeList but got %s.", elements.getLength())); + "Expected 1 element \"%s\" in NodeList but got %s.", + tagName, elements.getLength())); } var elementAsNode = elements.item(0); if (!(elementAsNode instanceof Element)) { - throw new IllegalStateException( - String.format("%s was not an element.", elementAsNode.getNodeName())); + throw new MalformedXmlException( + String.format("%s was not a valid XML element.", tagName)); } return ((Element) elementAsNode); } diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java new file mode 100644 index 000000000000..216df56c453e --- /dev/null +++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/MalformedXmlException.java @@ -0,0 +1,33 @@ +/* + * 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.asllib.util; + +public class MalformedXmlException extends Exception { + /** Constructs an {@code MalformedXmlException} with no detail message. */ + public MalformedXmlException() { + super(); + } + + /** + * Constructs an {@code MalformedXmlException} with the specified detail message. + * + * @param s the detail message. + */ + public MalformedXmlException(String s) { + super(s); + } +} diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig index 6ac986e406a0..6c4e4c3eb9be 100644 --- a/wifi/wifi.aconfig +++ b/wifi/wifi.aconfig @@ -2,6 +2,7 @@ package: "android.net.wifi.flags" flag { name: "get_device_cross_akm_roaming_support" + is_exported: true namespace: "wifi" description: "Add new API to get the device support for CROSS-AKM roaming" bug: "313038031" |