diff options
580 files changed, 25711 insertions, 5575 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 625dca7e402e..45e33ce4b6e9 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -636,6 +636,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "aconfig_hardware_flags_c_lib", + aconfig_declarations: "android.hardware.flags-aconfig", +} + // Widget aconfig_declarations { name: "android.widget.flags-aconfig", @@ -803,21 +808,6 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } -// OnDeviceIntelligence -aconfig_declarations { - name: "android.app.ondeviceintelligence-aconfig", - exportable: true, - package: "android.app.ondeviceintelligence.flags", - container: "system", - srcs: ["core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig"], -} - -java_aconfig_library { - name: "android.app.ondeviceintelligence-aconfig-java", - aconfig_declarations: "android.app.ondeviceintelligence-aconfig", - defaults: ["framework-minus-apex-aconfig-java-defaults"], -} - // Permissions aconfig_declarations { name: "android.permission.flags-aconfig", @@ -1477,6 +1467,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.appwidget.flags-aconfig-java-host", + aconfig_declarations: "android.appwidget.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // App aconfig_declarations { name: "android.server.app.flags-aconfig", diff --git a/Android.bp b/Android.bp index a525583b8ba0..529da53e58f7 100644 --- a/Android.bp +++ b/Android.bp @@ -446,6 +446,9 @@ java_library { default: [ "framework-platformcrashrecovery.impl", ], + }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), { + true: [], + default: ["framework-ondeviceintelligence-platform.impl"], }), sdk_version: "core_platform", installable: false, @@ -489,6 +492,7 @@ java_library { apex_available: ["//apex_available:platform"], visibility: [ "//frameworks/base:__subpackages__", + "//packages/modules/NeuralNetworks:__subpackages__", ], compile_dex: false, headers_only: true, @@ -584,6 +588,9 @@ java_library { default: [ "framework-platformcrashrecovery-compat-config", ], + }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), { + true: [], + default: ["framework-ondeviceintelligence-platform-compat-config"], }), } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index fe80d1be9532..8fad79a845b4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -5926,9 +5926,6 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT, Flags.doNotForceRushExecutionAtBoot()); pw.println(); - pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION, - android.app.job.Flags.backupJobsExemption()); - pw.println(); pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND, android.app.job.Flags.ignoreImportantWhileForeground()); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index f3bc9c747f17..42c8250a6185 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -433,9 +433,6 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler { case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT: pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot()); break; - case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION: - pw.println(android.app.job.Flags.backupJobsExemption()); - break; case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND: pw.println(android.app.job.Flags.ignoreImportantWhileForeground()); break; diff --git a/api/Android.bp b/api/Android.bp index 73262030ee37..14c2766d8887 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -105,6 +105,13 @@ combined_apis { default: [ "framework-platformcrashrecovery", ], + }) + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), { + true: [ + "framework-ondeviceintelligence", + ], + default: [ + "framework-ondeviceintelligence-platform", + ], }) + select(release_flag("RELEASE_RANGING_STACK"), { true: [ "framework-ranging", @@ -119,7 +126,12 @@ combined_apis { "service-permission", "service-rkp", "service-sdksandbox", - ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { + ] + select(release_flag("RELEASE_ONDEVICE_INTELLIGENCE_MODULE"), { + true: [ + "service-ondeviceintelligence", + ], + default: [], + }) + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), { "true": [ "service-crashrecovery", ], @@ -478,6 +490,7 @@ java_defaults { "//frameworks/base/location", "//frameworks/base/packages/CrashRecovery/framework", "//frameworks/base/nfc", + "//packages/modules/NeuralNetworks:__subpackages__", ], plugins: ["error_prone_android_framework"], errorprone: { diff --git a/api/api.go b/api/api.go index 5ca24de1b46a..e4d783eba4c3 100644 --- a/api/api.go +++ b/api/api.go @@ -29,6 +29,7 @@ const i18n = "i18n.module.public.api" const virtualization = "framework-virtualization" const location = "framework-location" const platformCrashrecovery = "framework-platformcrashrecovery" +const ondeviceintelligence = "framework-ondeviceintelligence-platform" var core_libraries_modules = []string{art, conscrypt, i18n} @@ -40,7 +41,7 @@ var core_libraries_modules = []string{art, conscrypt, i18n} // APIs. // In addition, the modules in this list are allowed to contribute to test APIs // stubs. -var non_updatable_modules = []string{virtualization, location, platformCrashrecovery} +var non_updatable_modules = []string{virtualization, location, platformCrashrecovery, ondeviceintelligence} // The intention behind this soong plugin is to generate a number of "merged" // API-related modules that would otherwise require a large amount of very diff --git a/boot/Android.bp b/boot/Android.bp index 6eead42a4d30..eaa984ac0cdd 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -31,6 +31,7 @@ soong_config_module_type { "car_bootclasspath_fragment", "nfc_apex_bootclasspath_fragment", "release_crashrecovery_module", + "release_ondevice_intelligence_module", "release_package_profiling_module", ], properties: [ @@ -176,6 +177,15 @@ custom_platform_bootclasspath { }, ], }, + release_ondevice_intelligence_module: { + fragments: [ + // only used when ondeviceintelligence is moved to neuralnetworks module + { + apex: "com.android.neuralnetworks", + module: "com.android.ondeviceintelligence-bootclasspath-fragment", + }, + ], + }, release_package_profiling_module: { fragments: [ // only used if profiling is enabled. diff --git a/core/api/current.txt b/core/api/current.txt index c6e53e8dd5cc..bbdcd6de9cb0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6482,6 +6482,7 @@ package android.app { method public String getSortKey(); method public long getTimeoutAfter(); method public boolean hasImage(); + method @FlaggedApi("android.app.api_rich_ongoing") public boolean hasPromotableCharacteristics(); method public void writeToParcel(android.os.Parcel, int); field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT; field public static final int BADGE_ICON_LARGE = 2; // 0x2 @@ -8883,6 +8884,7 @@ package android.app.appfunctions { field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final int ERROR_DENIED = 1000; // 0x3e8 field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2 field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 @@ -27168,6 +27170,15 @@ package android.media.projection { package android.media.quality { + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class ActiveProcessingPicture implements android.os.Parcelable { + ctor public ActiveProcessingPicture(int, @NonNull String); + method public int describeContents(); + method public int getId(); + method @NonNull public String getProfileId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.ActiveProcessingPicture> CREATOR; + } + @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class AmbientBacklightEvent implements android.os.Parcelable { ctor public AmbientBacklightEvent(int, @Nullable android.media.quality.AmbientBacklightMetadata); method public int describeContents(); @@ -27219,8 +27230,30 @@ package android.media.quality { } public static final class MediaQualityContract.PictureQuality { + field public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED = "auto_picture_quality_enabled"; + field public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = "auto_super_resolution_enabled"; + field public static final String PARAMETER_BLUE_STRETCH = "blue_stretch"; field public static final String PARAMETER_BRIGHTNESS = "brightness"; + field public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature"; + field public static final String PARAMETER_COLOR_TUNE = "color_tune"; + field public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain"; + field public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset"; + field public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness"; + field public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain"; + field public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset"; + field public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue"; + field public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain"; + field public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset"; + field public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation"; field public static final String PARAMETER_CONTRAST = "contrast"; + field public static final String PARAMETER_DECONTOUR = "decontour"; + field public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control"; + field public static final String PARAMETER_FILM_MODE = "film_mode"; + field public static final String PARAMETER_FLESH_TONE = "flesh_tone"; + field public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming"; + field public static final String PARAMETER_HUE = "hue"; + field public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction"; + field public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction"; field public static final String PARAMETER_SATURATION = "saturation"; field public static final String PARAMETER_SHARPNESS = "sharpness"; } @@ -27232,13 +27265,14 @@ package android.media.quality { } @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method public void addActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener); method public void createPictureProfile(@NonNull android.media.quality.PictureProfile); method public void createSoundProfile(@NonNull android.media.quality.SoundProfile); - method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles(); - method @NonNull public java.util.List<android.media.quality.SoundProfile> getAvailableSoundProfiles(); + method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles(boolean); + method @NonNull public java.util.List<android.media.quality.SoundProfile> getAvailableSoundProfiles(boolean); method @NonNull public java.util.List<android.media.quality.ParamCapability> getParamCapabilities(@NonNull java.util.List<java.lang.String>); - method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String); - method @Nullable public android.media.quality.SoundProfile getSoundProfile(int, @NonNull String); + method @Nullable public android.media.quality.PictureProfile getPictureProfile(int, @NonNull String, boolean); + method @Nullable public android.media.quality.SoundProfile getSoundProfile(int, @NonNull String, boolean); method public boolean isAmbientBacklightEnabled(); method public boolean isAutoPictureQualityEnabled(); method public boolean isAutoSoundQualityEnabled(); @@ -27246,6 +27280,7 @@ package android.media.quality { method public void registerAmbientBacklightCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.AmbientBacklightCallback); method public void registerPictureProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.PictureProfileCallback); method public void registerSoundProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.SoundProfileCallback); + method public void removeActiveProcessingPictureListener(@NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener); method public void removePictureProfile(@NonNull String); method public void removeSoundProfile(@NonNull String); method public void setAmbientBacklightEnabled(boolean); @@ -27257,6 +27292,10 @@ package android.media.quality { method public void updateSoundProfile(@NonNull String, @NonNull android.media.quality.SoundProfile); } + public static interface MediaQualityManager.ActiveProcessingPictureListener { + method public void onActiveProcessingPicturesChanged(@NonNull java.util.List<android.media.quality.ActiveProcessingPicture>); + } + public abstract static class MediaQualityManager.AmbientBacklightCallback { ctor public MediaQualityManager.AmbientBacklightCallback(); method public void onAmbientBacklightEvent(@NonNull android.media.quality.AmbientBacklightEvent); @@ -27264,7 +27303,7 @@ package android.media.quality { public abstract static class MediaQualityManager.PictureProfileCallback { ctor public MediaQualityManager.PictureProfileCallback(); - method public void onError(int); + method public void onError(@Nullable String, int); method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List<android.media.quality.ParamCapability>); method public void onPictureProfileAdded(@NonNull String, @NonNull android.media.quality.PictureProfile); method public void onPictureProfileRemoved(@NonNull String, @NonNull android.media.quality.PictureProfile); @@ -27273,7 +27312,7 @@ package android.media.quality { public abstract static class MediaQualityManager.SoundProfileCallback { ctor public MediaQualityManager.SoundProfileCallback(); - method public void onError(int); + method public void onError(@Nullable String, int); method public void onParamCapabilitiesChanged(@Nullable String, @NonNull java.util.List<android.media.quality.ParamCapability>); method public void onSoundProfileAdded(@NonNull String, @NonNull android.media.quality.SoundProfile); method public void onSoundProfileRemoved(@NonNull String, @NonNull android.media.quality.SoundProfile); @@ -34726,7 +34765,10 @@ package android.os { method public android.os.MessageQueue getMessageQueue(); method public boolean hasMessages(android.os.Handler, Object, int); method public boolean hasMessages(android.os.Handler, Object, Runnable); + method @FlaggedApi("android.os.message_queue_testability") public boolean isBlockedOnSyncBarrier(); method public android.os.Message next(); + method @FlaggedApi("android.os.message_queue_testability") @Nullable public Long peekWhen(); + method @FlaggedApi("android.os.message_queue_testability") @Nullable public android.os.Message pop(); method public void recycle(android.os.Message); method public void release(); } @@ -41006,7 +41048,7 @@ package android.service.autofill { field public static final int TYPE_DATASET_SELECTED = 0; // 0x0 field public static final int TYPE_SAVE_SHOWN = 3; // 0x3 field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6 - field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4 + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDENTIAL_MANAGER = 4; // 0x4 field public static final int UI_TYPE_DIALOG = 3; // 0x3 field public static final int UI_TYPE_INLINE = 2; // 0x2 field public static final int UI_TYPE_MENU = 1; // 0x1 @@ -44846,11 +44888,11 @@ package android.telephony { field public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array"; field public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; field public static final String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool"; - field public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int"; - field public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; - field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; - field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; - field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; field public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array"; field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int"; field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool"; @@ -44877,7 +44919,7 @@ package android.telephony { field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string"; field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; - field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL = "disable_dun_apn_while_roaming_with_preset_apn_bool"; field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool"; @@ -44998,6 +45040,7 @@ package android.telephony { field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array"; field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = "regional_satellite_earfcn_bundle"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool"; field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool"; field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool"; @@ -45011,6 +45054,7 @@ package android.telephony { field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool"; @@ -45022,12 +45066,14 @@ package android.telephony { field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_screen_off_inactivity_timeout_sec_int"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = "satellite_roaming_turn_off_session_for_emergency_call_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT = "satellite_sos_max_datagram_size_bytes_int"; + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY = "satellite_supported_msg_apps_string_array"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; - field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; field public static final String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool"; field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool"; - field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; field public static final String KEY_SHOW_FORWARDED_NUMBER_BOOL = "show_forwarded_number_bool"; field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool"; @@ -45057,7 +45103,7 @@ package android.telephony { field public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool"; field public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool"; field public static final String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; - field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool"; field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array"; field public static final String KEY_SWITCH_DATA_TO_PRIMARY_IF_PRIMARY_IS_OOS_BOOL = "switch_data_to_primary_if_primary_is_oos_bool"; @@ -45067,7 +45113,7 @@ package android.telephony { field public static final String KEY_USE_ACS_FOR_RCS_BOOL = "use_acs_for_rcs_bool"; field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool"; field public static final String KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL = "use_ip_for_calling_indicator_bool"; - field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; field @Deprecated public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool"; field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool"; field public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool"; @@ -45503,13 +45549,13 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentity> CREATOR; } - public final class CellIdentityCdma extends android.telephony.CellIdentity { - method public int getBasestationId(); - method public int getLatitude(); - method public int getLongitude(); - method public int getNetworkId(); - method public int getSystemId(); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; + @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public final class CellIdentityCdma extends android.telephony.CellIdentity { + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getBasestationId(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getLatitude(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getLongitude(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getNetworkId(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getSystemId(); + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR; } public final class CellIdentityGsm extends android.telephony.CellIdentity { @@ -45601,11 +45647,11 @@ package android.telephony { field public static final long UNAVAILABLE_LONG = 9223372036854775807L; // 0x7fffffffffffffffL } - public final class CellInfoCdma extends android.telephony.CellInfo implements android.os.Parcelable { - method @NonNull public android.telephony.CellIdentityCdma getCellIdentity(); - method @NonNull public android.telephony.CellSignalStrengthCdma getCellSignalStrength(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoCdma> CREATOR; + @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public final class CellInfoCdma extends android.telephony.CellInfo implements android.os.Parcelable { + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public android.telephony.CellIdentityCdma getCellIdentity(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public android.telephony.CellSignalStrengthCdma getCellSignalStrength(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public void writeToParcel(android.os.Parcel, int); + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellInfoCdma> CREATOR; } public final class CellInfoGsm extends android.telephony.CellInfo implements android.os.Parcelable { @@ -47184,11 +47230,11 @@ package android.telephony { method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number(); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn(); - method @Nullable public String getManufacturerCode(); - method @Nullable public String getManufacturerCode(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @Nullable public String getManufacturerCode(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @Nullable public String getManufacturerCode(int); method public static long getMaximumCallComposerPictureSize(); - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(); - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getMeid(int); method public String getMmsUAProfUrl(); method public String getMmsUserAgent(); method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getNai(); @@ -47334,10 +47380,10 @@ package android.telephony { field public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED = 2; // 0x2 field public static final int CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER = 3; // 0x3 field public static final int CARRIER_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0 - field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1 - field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2 - field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0 - field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4 field public static final int DATA_ACTIVITY_IN = 1; // 0x1 field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3 @@ -47357,9 +47403,9 @@ package android.telephony { field public static final int DATA_SUSPENDED = 3; // 0x3 field public static final int DATA_UNKNOWN = -1; // 0xffffffff field public static final int DEFAULT_PORT_INDEX = 0; // 0x0 - field public static final int ERI_FLASH = 2; // 0x2 - field public static final int ERI_OFF = 1; // 0x1 - field public static final int ERI_ON = 0; // 0x0 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int ERI_FLASH = 2; // 0x2 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int ERI_OFF = 1; // 0x1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int ERI_ON = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_EMERGENCY_MESSAGE = "android.telephony.event.DISPLAY_EMERGENCY_MESSAGE"; field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT"; field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL"; @@ -47400,11 +47446,11 @@ package android.telephony { field public static final int NETWORK_SELECTION_MODE_AUTO = 1; // 0x1 field public static final int NETWORK_SELECTION_MODE_MANUAL = 2; // 0x2 field public static final int NETWORK_SELECTION_MODE_UNKNOWN = 0; // 0x0 - field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_1xRTT = 7; // 0x7 field public static final long NETWORK_TYPE_BITMASK_1xRTT = 64L; // 0x40L - field public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final long NETWORK_TYPE_BITMASK_CDMA = 8L; // 0x8L field public static final long NETWORK_TYPE_BITMASK_EDGE = 2L; // 0x2L - field public static final long NETWORK_TYPE_BITMASK_EHRPD = 8192L; // 0x2000L + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final long NETWORK_TYPE_BITMASK_EHRPD = 8192L; // 0x2000L field public static final long NETWORK_TYPE_BITMASK_EVDO_0 = 16L; // 0x10L field public static final long NETWORK_TYPE_BITMASK_EVDO_A = 32L; // 0x20L field public static final long NETWORK_TYPE_BITMASK_EVDO_B = 2048L; // 0x800L @@ -47417,16 +47463,17 @@ package android.telephony { field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final long NETWORK_TYPE_BITMASK_NB_IOT_NTN = 1048576L; // 0x100000L field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L - field public static final int NETWORK_TYPE_CDMA = 4; // 0x4 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_CDMA = 4; // 0x4 field public static final int NETWORK_TYPE_EDGE = 2; // 0x2 - field public static final int NETWORK_TYPE_EHRPD = 14; // 0xe - field public static final int NETWORK_TYPE_EVDO_0 = 5; // 0x5 - field public static final int NETWORK_TYPE_EVDO_A = 6; // 0x6 - field public static final int NETWORK_TYPE_EVDO_B = 12; // 0xc + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_EHRPD = 14; // 0xe + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_EVDO_0 = 5; // 0x5 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_EVDO_A = 6; // 0x6 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int NETWORK_TYPE_EVDO_B = 12; // 0xc field public static final int NETWORK_TYPE_GPRS = 1; // 0x1 field public static final int NETWORK_TYPE_GSM = 16; // 0x10 field public static final int NETWORK_TYPE_HSDPA = 8; // 0x8 @@ -47436,11 +47483,12 @@ package android.telephony { field @Deprecated public static final int NETWORK_TYPE_IDEN = 11; // 0xb field public static final int NETWORK_TYPE_IWLAN = 18; // 0x12 field public static final int NETWORK_TYPE_LTE = 13; // 0xd + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int NETWORK_TYPE_NB_IOT_NTN = 21; // 0x15 field public static final int NETWORK_TYPE_NR = 20; // 0x14 field public static final int NETWORK_TYPE_TD_SCDMA = 17; // 0x11 field public static final int NETWORK_TYPE_UMTS = 3; // 0x3 field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0 - field public static final int PHONE_TYPE_CDMA = 2; // 0x2 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int PHONE_TYPE_CDMA = 2; // 0x2 field public static final int PHONE_TYPE_GSM = 1; // 0x1 field public static final int PHONE_TYPE_NONE = 0; // 0x0 field public static final int PHONE_TYPE_SIP = 3; // 0x3 @@ -55204,8 +55252,8 @@ package android.view { method public abstract void setTransformation(android.graphics.Matrix); method public abstract void setVisibility(int); method public abstract void setWebDomain(@Nullable String); - field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; - field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.extra.VIRTUAL_STRUCTURE_TYPE"; + field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; } public abstract static class ViewStructure.HtmlInfo { diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index ad5bd31828e0..e71dffaf152d 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -1,10 +1,4 @@ // Baseline format: 1.0 -ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE: - Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE` -ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER: - Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER` - - BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED: Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED: @@ -1191,10 +1185,6 @@ UnflaggedApi: android.R.dimen#system_corner_radius_xlarge: New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge UnflaggedApi: android.R.dimen#system_corner_radius_xsmall: New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall -UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum: - Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum -UnflaggedApi: android.R.string#status_bar_notification_info_overflow: - Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR: New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID: diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b8d272a0dd12..36f3519f426d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -329,6 +329,7 @@ package android { field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES"; field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS"; field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES"; + field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final String READ_SUBSCRIPTION_PLANS = "android.permission.READ_SUBSCRIPTION_PLANS"; field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"; field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO"; field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL"; @@ -2277,149 +2278,6 @@ package android.app.job { } -package android.app.ondeviceintelligence { - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback { - method public void onDownloadCompleted(@NonNull android.os.PersistableBundle); - method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle); - method public default void onDownloadProgress(long); - method public default void onDownloadStarted(long); - field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3 - field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2 - field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1 - field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4 - field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0 - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.os.PersistableBundle getFeatureParams(); - method public int getId(); - method @Nullable public String getModelName(); - method @Nullable public String getName(); - method public int getType(); - method public int getVariant(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR; - } - - public static final class Feature.Builder { - ctor public Feature.Builder(int); - method @NonNull public android.app.ondeviceintelligence.Feature build(); - method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle); - method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String); - method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String); - method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int); - method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int); - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable { - ctor public FeatureDetails(int, @NonNull android.os.PersistableBundle); - ctor public FeatureDetails(int); - method public int describeContents(); - method @NonNull public android.os.PersistableBundle getFeatureDetailParams(); - method public int getFeatureStatus(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR; - field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3 - field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1 - field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2 - field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4 - field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0 - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") public final class InferenceInfo implements android.os.Parcelable { - method public int describeContents(); - method public long getEndTimeMillis(); - method public long getStartTimeMillis(); - method public long getSuspendedTimeMillis(); - method public int getUid(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.InferenceInfo> CREATOR; - } - - public static final class InferenceInfo.Builder { - ctor public InferenceInfo.Builder(int); - method @NonNull public android.app.ondeviceintelligence.InferenceInfo build(); - method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setEndTimeMillis(long); - method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setStartTimeMillis(long); - method @NonNull public android.app.ondeviceintelligence.InferenceInfo.Builder setSuspendedTimeMillis(long); - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceException extends java.lang.Exception { - ctor public OnDeviceIntelligenceException(int, @NonNull String, @NonNull android.os.PersistableBundle); - ctor public OnDeviceIntelligenceException(int, @NonNull android.os.PersistableBundle); - ctor public OnDeviceIntelligenceException(int, @NonNull String); - ctor public OnDeviceIntelligenceException(int); - method public int getErrorCode(); - method @NonNull public android.os.PersistableBundle getErrorParams(); - field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 100; // 0x64 - field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2 - field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3 - field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9 - field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7 - field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5 - field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe - field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6 - field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8 - field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4 - field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc - field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb - field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa - field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf - field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd - field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1 - field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 200; // 0xc8 - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class OnDeviceIntelligenceManager { - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence_module") @NonNull @RequiresPermission(android.Manifest.permission.DUMP) public java.util.List<android.app.ondeviceintelligence.InferenceInfo> getLatestInferenceInfo(long); - method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName(); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingCallback); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback); - method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2 - field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0 - field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1 - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingCallback { - method public default void onDataAugmentRequest(@NonNull android.os.Bundle, @NonNull java.util.function.Consumer<android.os.Bundle>); - method public void onError(@NonNull android.app.ondeviceintelligence.OnDeviceIntelligenceException); - method public void onResult(@NonNull android.os.Bundle); - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal { - ctor public ProcessingSignal(); - method public void sendSignal(@NonNull android.os.PersistableBundle); - method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback); - } - - public static interface ProcessingSignal.OnProcessingSignalCallback { - method public void onSignalReceived(@NonNull android.os.PersistableBundle); - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingProcessingCallback extends android.app.ondeviceintelligence.ProcessingCallback { - method public void onPartialResult(@NonNull android.os.Bundle); - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable { - ctor public TokenInfo(long, @NonNull android.os.PersistableBundle); - ctor public TokenInfo(long); - method public int describeContents(); - method public long getCount(); - method @NonNull public android.os.PersistableBundle getInfoParams(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR; - } - -} - package android.app.people { public final class PeopleManager { @@ -8130,12 +7988,13 @@ package android.media.musicrecognition { package android.media.quality { @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager { + method public void addGlobalActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener); method @NonNull public java.util.List<java.lang.String> getPictureProfileAllowList(); method @NonNull public java.util.List<java.lang.String> getPictureProfilePackageNames(); - method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String); + method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String, boolean); method @NonNull public java.util.List<java.lang.String> getSoundProfileAllowList(); method @NonNull public java.util.List<java.lang.String> getSoundProfilePackageNames(); - method @NonNull public java.util.List<android.media.quality.SoundProfile> getSoundProfilesByPackage(@NonNull String); + method @NonNull public java.util.List<android.media.quality.SoundProfile> getSoundProfilesByPackage(@NonNull String, boolean); method public void setAutoPictureQualityEnabled(boolean); method public void setAutoSoundQualityEnabled(boolean); method public boolean setDefaultPictureProfile(@Nullable String); @@ -13792,39 +13651,6 @@ package android.service.oemlock { } -package android.service.ondeviceintelligence { - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service { - ctor public OnDeviceIntelligenceService(); - method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback); - method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>); - method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer); - method public abstract void onInferenceServiceConnected(); - method public abstract void onInferenceServiceDisconnected(); - method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; - } - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service { - ctor public OnDeviceSandboxedInferenceService(); - method public final void fetchFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>); - method @NonNull public java.util.concurrent.Executor getCallbackExecutor(); - method public final void getReadOnlyFileDescriptor(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.ParcelFileDescriptor>) throws java.io.FileNotFoundException; - method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); - method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingCallback); - method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingProcessingCallback); - method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.app.ondeviceintelligence.OnDeviceIntelligenceException>); - method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException; - field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService"; - } - -} - package android.service.persistentdata { @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager { @@ -14805,7 +14631,7 @@ package android.telecom { field public static final int CALLTYPE_INCOMING = 1; // 0x1 field public static final int CALLTYPE_OUTGOING = 2; // 0x2 field public static final int CALLTYPE_UNKNOWN = 0; // 0x0 - field public static final int CDMA_PHONE = 1; // 0x1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_PHONE = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics> CREATOR; field public static final int GSM_PHONE = 2; // 0x2 field public static final int IMS_PHONE = 4; // 0x4 @@ -15186,7 +15012,7 @@ package android.telephony { field public static final String KEY_GBA_UA_SECURITY_ORGANIZATION_INT = "gba_ua_security_organization_int"; field public static final String KEY_GBA_UA_SECURITY_PROTOCOL_INT = "gba_ua_security_protocol_int"; field public static final String KEY_GBA_UA_TLS_CIPHER_SUITE_INT = "gba_ua_tls_cipher_suite_int"; - field public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool"; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool"; } public static final class CarrierConfigManager.Wifi { @@ -15273,8 +15099,8 @@ package android.telephony { ctor public CellBroadcastService(); method @NonNull @WorkerThread public abstract CharSequence getCellBroadcastAreaInfo(int); method @CallSuper public android.os.IBinder onBind(@Nullable android.content.Intent); - method public abstract void onCdmaCellBroadcastSms(int, @NonNull byte[], int); - method public abstract void onCdmaScpMessage(int, @NonNull java.util.List<android.telephony.cdma.CdmaSmsCbProgramData>, @NonNull String, @NonNull java.util.function.Consumer<android.os.Bundle>); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public void onCdmaCellBroadcastSms(int, @NonNull byte[], int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public void onCdmaScpMessage(int, @NonNull java.util.List<android.telephony.cdma.CdmaSmsCbProgramData>, @NonNull String, @NonNull java.util.function.Consumer<android.os.Bundle>); method public abstract void onGsmCellBroadcastSms(int, @NonNull byte[]); field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService"; } @@ -15284,9 +15110,9 @@ package android.telephony { method @NonNull public abstract android.telephony.CellIdentity sanitizeLocationInfo(); } - public final class CellIdentityCdma extends android.telephony.CellIdentity { - method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation(); - method @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo(); + @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public final class CellIdentityCdma extends android.telephony.CellIdentity { + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo(); } public final class CellIdentityGsm extends android.telephony.CellIdentity { @@ -15702,16 +15528,16 @@ package android.telephony { field public static final int BUSY = 17; // 0x11 field public static final int CALL_BARRED = 240; // 0xf0 field public static final int CALL_REJECTED = 21; // 0x15 - field public static final int CDMA_ACCESS_BLOCKED = 1009; // 0x3f1 - field public static final int CDMA_ACCESS_FAILURE = 1006; // 0x3ee - field public static final int CDMA_DROP = 1001; // 0x3e9 - field public static final int CDMA_INTERCEPT = 1002; // 0x3ea - field public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; // 0x3e8 - field public static final int CDMA_NOT_EMERGENCY = 1008; // 0x3f0 - field public static final int CDMA_PREEMPTED = 1007; // 0x3ef - field public static final int CDMA_REORDER = 1003; // 0x3eb - field public static final int CDMA_RETRY_ORDER = 1005; // 0x3ed - field public static final int CDMA_SO_REJECT = 1004; // 0x3ec + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ACCESS_BLOCKED = 1009; // 0x3f1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_ACCESS_FAILURE = 1006; // 0x3ee + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_DROP = 1001; // 0x3e9 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_INTERCEPT = 1002; // 0x3ea + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; // 0x3e8 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_NOT_EMERGENCY = 1008; // 0x3f0 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_PREEMPTED = 1007; // 0x3ef + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_REORDER = 1003; // 0x3eb + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_RETRY_ORDER = 1005; // 0x3ed + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_SO_REJECT = 1004; // 0x3ec field public static final int CHANNEL_NOT_AVAIL = 44; // 0x2c field public static final int CHANNEL_UNACCEPTABLE = 6; // 0x6 field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64 @@ -16245,14 +16071,14 @@ package android.telephony { method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageName(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageNameForLogicalSlot(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaEnhancedRoamingIndicatorDisplayNumber(); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int); - method public String getCdmaPrlVersion(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode(); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaSubscriptionMode(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaEnhancedRoamingIndicatorDisplayNumber(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public String getCdmaPrlVersion(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaSubscriptionMode(); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS) public java.util.List<android.telephony.CellBroadcastIdRange> getCellBroadcastIdRanges(); method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); @@ -16342,7 +16168,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void resetIms(int); method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void resetOtaEmergencyNumberDbFilePath(); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig(); method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int sendThermalMitigationRequest(@NonNull android.telephony.ThermalMitigationRequest); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); @@ -16351,8 +16177,8 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallWaitingEnabled(boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCdmaRoamingMode(int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCdmaSubscriptionMode(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCdmaRoamingMode(int); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCdmaSubscriptionMode(int); method @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS) public void setCellBroadcastIdRanges(@NonNull java.util.List<android.telephony.CellBroadcastIdRange>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean); @@ -16416,9 +16242,9 @@ package android.telephony { field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1 field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0 field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff - field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1 - field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 - field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff field public static final int CELL_BROADCAST_RESULT_FAIL_ACTIVATION = 3; // 0x3 field public static final int CELL_BROADCAST_RESULT_FAIL_CONFIG = 2; // 0x2 field public static final int CELL_BROADCAST_RESULT_SUCCESS = 0; // 0x0 @@ -16635,21 +16461,21 @@ package android.telephony { package android.telephony.cdma { - public final class CdmaSmsCbProgramData implements android.os.Parcelable { - method public int describeContents(); - method public int getCategory(); - method public int getOperation(); - method public void writeToParcel(android.os.Parcel, int); - field public static final int CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 4099; // 0x1003 - field public static final int CATEGORY_CMAS_EXTREME_THREAT = 4097; // 0x1001 - field public static final int CATEGORY_CMAS_LAST_RESERVED_VALUE = 4351; // 0x10ff - field public static final int CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 4096; // 0x1000 - field public static final int CATEGORY_CMAS_SEVERE_THREAT = 4098; // 0x1002 - field public static final int CATEGORY_CMAS_TEST_MESSAGE = 4100; // 0x1004 - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.cdma.CdmaSmsCbProgramData> CREATOR; - field public static final int OPERATION_ADD_CATEGORY = 1; // 0x1 - field public static final int OPERATION_CLEAR_CATEGORIES = 2; // 0x2 - field public static final int OPERATION_DELETE_CATEGORY = 0; // 0x0 + @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public final class CdmaSmsCbProgramData implements android.os.Parcelable { + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int describeContents(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getCategory(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public int getOperation(); + method @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public void writeToParcel(android.os.Parcel, int); + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 4099; // 0x1003 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_EXTREME_THREAT = 4097; // 0x1001 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_LAST_RESERVED_VALUE = 4351; // 0x10ff + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 4096; // 0x1000 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_SEVERE_THREAT = 4098; // 0x1002 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CATEGORY_CMAS_TEST_MESSAGE = 4100; // 0x1004 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") @NonNull public static final android.os.Parcelable.Creator<android.telephony.cdma.CdmaSmsCbProgramData> CREATOR; + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int OPERATION_ADD_CATEGORY = 1; // 0x1 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int OPERATION_CLEAR_CATEGORIES = 2; // 0x2 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int OPERATION_DELETE_CATEGORY = 0; // 0x0 } } @@ -17977,7 +17803,7 @@ package android.telephony.ims { field public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 1; // 0x1 field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 7; // 0x7 field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 6; // 0x6 - field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; // 0x4 + field @Deprecated @FlaggedApi("com.android.internal.telephony.flags.deprecate_cdma") public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; // 0x4 field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 5; // 0x5 field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_INTERNET_PDN = 12; // 0xc field public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 9; // 0x9 @@ -18826,6 +18652,10 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } + @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SatelliteDisallowedReasonsCallback { + method public void onSatelliteDisallowedReasonsChanged(@NonNull int[]); + } + @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SatelliteInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List<java.lang.Integer> getBands(); @@ -18841,6 +18671,7 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons(); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); @@ -18851,6 +18682,8 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForSatelliteDisallowedReasonsChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSelectedNbIotSatelliteSubscriptionChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback); method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSupportedStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteSupportedStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); @@ -18864,7 +18697,10 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAccessConfigurationForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteAccessConfiguration,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteDisplayName(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.CharSequence,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteSubscriberProvisionStatus(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.telephony.satellite.SatelliteSubscriberProvisionStatus>,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSelectedNbIotSatelliteSubscriptionId(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Integer,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); @@ -18876,6 +18712,8 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback); method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDisallowedReasonsChanged(@NonNull android.telephony.satellite.SatelliteDisallowedReasonsCallback); + method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(@NonNull android.telephony.satellite.SelectedNbIotSatelliteSubscriptionCallback); method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSupportedStateChanged(@NonNull android.telephony.satellite.SatelliteSupportedStateCallback); field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION = "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION"; field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED = "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED"; @@ -18947,6 +18785,7 @@ package android.telephony.satellite { field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc + field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30; // 0x1e field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 @@ -19049,6 +18888,10 @@ package android.telephony.satellite { method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public default void onSendDatagramStateChanged(int, int, int, int); } + @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public interface SelectedNbIotSatelliteSubscriptionCallback { + method public void onSelectedNbIotSatelliteSubscriptionChanged(int); + } + @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public final class SystemSelectionSpecifier implements android.os.Parcelable { method public int describeContents(); method @NonNull public int[] getBands(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 0a806c76a24b..6230a59a62c0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3247,14 +3247,6 @@ package android.service.notification { } -package android.service.ondeviceintelligence { - - @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service { - method public void onReady(); - } - -} - package android.service.quickaccesswallet { public interface QuickAccessWalletClient extends java.io.Closeable { diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index 9140bdf4fba7..349b4edffc32 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -1,10 +1,4 @@ // Baseline format: 1.0 -ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE: - Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE` -ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER: - Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER` - - BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer): Methods must not throw unchecked exceptions diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 38aea64386a0..03ef669c0675 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1279,7 +1279,24 @@ public class Activity extends ContextThemeWrapper * * <p>This method should be utilized when an activity wants to nudge the user to switch * to the web application in cases where the web may provide the user with a better - * experience. Note that this method does not guarantee that the education will be shown.</p> + * experience. Note that this method does not guarantee that the education will be shown. + * + * <p>The number of times that the "Open in browser" education can be triggered by this method + * is limited per application, and, when shown, the education appears above the app's content. + * For these reasons, developers should use this method sparingly when it is least + * disruptive to the user to show the education and when it is optimal to switch the user to a + * browser session. Before requesting to show the education, developers should assert that they + * have set a link that can be used by the "Open in browser" feature through either + * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or + * {@link AssistContent#setWebUri} so that users are navigated to a relevant page if they choose + * to switch to the browser. If a URI is not set using either method, "Open in browser" will + * utilize a generic link if available which will direct users to the homepage of the site + * associated with the app. The generic link is provided for a limited number of applications by + * the system and cannot be edited by developers. If none of these options contains a valid URI, + * the user will not be provided with the option to switch to the browser and the education will + * not be shown if requested. + * + * @see android.app.assist.AssistContent#EXTRA_SESSION_TRANSFER_WEB_URI */ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION) public final void requestOpenInBrowserEducation() { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 33ba05865042..69d3e8d4c0d2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1189,6 +1189,18 @@ public class ActivityManager { return procState == PROCESS_STATE_FOREGROUND_SERVICE; } + /** @hide Should this process state be considered jank perceptible? */ + public static final boolean isProcStateJankPerceptible(int procState) { + if (Flags.jankPerceptibleNarrow()) { + return procState == PROCESS_STATE_PERSISTENT_UI + || procState == PROCESS_STATE_TOP + || procState == PROCESS_STATE_IMPORTANT_FOREGROUND + || procState == PROCESS_STATE_TOP_SLEEPING; + } else { + return !isProcStateCached(procState); + } + } + /** @hide requestType for assist context: only basic information. */ public static final int ASSIST_CONTEXT_BASIC = 0; diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index eccb6ffb281c..abdfb53537f8 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -44,6 +44,8 @@ import android.os.PowerExemptionManager.ReasonCode; import android.os.PowerExemptionManager.TempAllowListType; import android.os.TransactionTooLargeException; import android.os.WorkSource; +import android.os.instrumentation.IOffsetCallback; +import android.os.instrumentation.MethodDescriptor; import android.util.ArraySet; import android.util.Pair; @@ -1352,6 +1354,14 @@ public abstract class ActivityManagerInternal { String reason, int exitInfoReason); /** + * Queries the offset data for a given method on a process. + * @hide + */ + public abstract void getExecutableMethodFileOffsets(@NonNull String processName, + int pid, int uid, @NonNull MethodDescriptor methodDescriptor, + @NonNull IOffsetCallback callback); + + /** * Add a creator token for all embedded intents (stored as extra) of the given intent. * * @param intent The given intent diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 27661ce21656..48b74f2d0776 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -165,6 +165,10 @@ import android.os.TelephonyServiceManager; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.IOffsetCallback; +import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.MethodDescriptorParser; import android.permission.IPermissionManager; import android.provider.BlockedNumberContract; import android.provider.CalendarContract; @@ -2236,6 +2240,29 @@ public final class ActivityThread extends ClientTransactionHandler args.arg6 = uiTranslationSpec; sendMessage(H.UPDATE_UI_TRANSLATION_STATE, args); } + + @Override + public void getExecutableMethodFileOffsets( + @NonNull MethodDescriptor methodDescriptor, + @NonNull IOffsetCallback resultCallback) { + Method method = MethodDescriptorParser.parseMethodDescriptor( + getClass().getClassLoader(), methodDescriptor); + VMDebug.ExecutableMethodFileOffsets location = + VMDebug.getExecutableMethodFileOffsets(method); + try { + if (location == null) { + resultCallback.onResult(null); + return; + } + ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets(); + ret.containerPath = location.getContainerPath(); + ret.containerOffset = location.getContainerOffset(); + ret.methodOffset = location.getMethodOffset(); + resultCallback.onResult(ret); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } private @NonNull SafeCancellationTransport createSafeCancellationTransport( @@ -3918,12 +3945,7 @@ public final class ActivityThread extends ClientTransactionHandler if (mLastProcessState == processState) { return; } - // Do not issue a transitional GC if we are transitioning between 2 cached states. - // Only update if the state flips between cached and uncached or vice versa - if (ActivityManager.isProcStateCached(mLastProcessState) - != ActivityManager.isProcStateCached(processState)) { - updateVmProcessState(processState); - } + updateVmProcessState(mLastProcessState, processState); mLastProcessState = processState; if (localLOGV) { Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState @@ -3932,18 +3954,21 @@ public final class ActivityThread extends ClientTransactionHandler } } + /** Converts a process state to a VM process state. */ + private static int toVmProcessState(int processState) { + final int state = ActivityManager.isProcStateJankPerceptible(processState) + ? VM_PROCESS_STATE_JANK_PERCEPTIBLE + : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE; + return state; + } + /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */ - // Currently ART VM only uses state updates for Transitional GC, and thus - // this function initiates a Transitional GC for transitions into Cached apps states. - private void updateVmProcessState(int processState) { - // Only a transition into Cached state should result in a Transitional GC request - // to the ART runtime. Update VM state to JANK_IMPERCEPTIBLE in that case. - // Note that there are 4 possible cached states currently, all of which are - // JANK_IMPERCEPTIBLE from GC point of view. - final int state = ActivityManager.isProcStateCached(processState) - ? VM_PROCESS_STATE_JANK_IMPERCEPTIBLE - : VM_PROCESS_STATE_JANK_PERCEPTIBLE; - VMRuntime.getRuntime().updateProcessState(state); + private void updateVmProcessState(int lastProcessState, int newProcessState) { + final int state = toVmProcessState(newProcessState); + if (lastProcessState == PROCESS_STATE_UNKNOWN + || state != toVmProcessState(lastProcessState)) { + VMRuntime.getRuntime().updateProcessState(state); + } } @Override diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl index 56ed290baf2e..b4dee2e937cb 100644 --- a/core/java/android/app/AppOpsManager.aidl +++ b/core/java/android/app/AppOpsManager.aidl @@ -19,7 +19,6 @@ package android.app; parcelable AppOpsManager.PackageOps; parcelable AppOpsManager.NoteOpEventProxyInfo; parcelable AppOpsManager.NoteOpEvent; -parcelable AppOpsManager.NotedOp; parcelable AppOpsManager.OpFeatureEntry; parcelable AppOpsManager.OpEntry; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index c789e28cdbab..19138126698c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -262,24 +262,6 @@ public class AppOpsManager { private static final Object sLock = new Object(); - // A map that records noted times for each op. - private final ArrayMap<NotedOp, Integer> mPendingNotedOps = new ArrayMap<>(); - private final HandlerThread mHandlerThread; - private final Handler mHandler; - private static final int NOTE_OP_BATCHING_DELAY_MILLIS = 1000; - - private boolean isNoteOpBatchingSupported() { - // If noteOp is called from system server no IPC is made, hence we don't need batching. - if (Process.myUid() == Process.SYSTEM_UID) { - return false; - } - return Flags.noteOpBatchingEnabled(); - } - - private final Object mBatchedNoteOpLock = new Object(); - @GuardedBy("mBatchedNoteOpLock") - private boolean mIsBatchedNoteOpCallScheduled = false; - /** Current {@link OnOpNotedCallback}. Change via {@link #setOnOpNotedCallback} */ @GuardedBy("sLock") private static @Nullable OnOpNotedCallback sOnOpNotedCallback; @@ -7484,135 +7466,6 @@ public class AppOpsManager { } /** - * A NotedOp is an app op grouped in noteOp API and sent to the system server in a batch - * - * @hide - */ - public static final class NotedOp implements Parcelable { - private final @IntRange(from = 0, to = _NUM_OP - 1) int mOp; - private final @IntRange(from = 0) int mUid; - private final @Nullable String mPackageName; - private final @Nullable String mAttributionTag; - private final int mVirtualDeviceId; - private final @Nullable String mMessage; - private final boolean mShouldCollectAsyncNotedOp; - private final boolean mShouldCollectMessage; - - public NotedOp(int op, int uid, @Nullable String packageName, - @Nullable String attributionTag, int virtualDeviceId, @Nullable String message, - boolean shouldCollectAsyncNotedOp, boolean shouldCollectMessage) { - mOp = op; - mUid = uid; - mPackageName = packageName; - mAttributionTag = attributionTag; - mVirtualDeviceId = virtualDeviceId; - mMessage = message; - mShouldCollectAsyncNotedOp = shouldCollectAsyncNotedOp; - mShouldCollectMessage = shouldCollectMessage; - } - - NotedOp(Parcel source) { - mOp = source.readInt(); - mUid = source.readInt(); - mPackageName = source.readString(); - mAttributionTag = source.readString(); - mVirtualDeviceId = source.readInt(); - mMessage = source.readString(); - mShouldCollectAsyncNotedOp = source.readBoolean(); - mShouldCollectMessage = source.readBoolean(); - } - - public int getOp() { - return mOp; - } - - public int getUid() { - return mUid; - } - - public @Nullable String getPackageName() { - return mPackageName; - } - - public @Nullable String getAttributionTag() { - return mAttributionTag; - } - - public int getVirtualDeviceId() { - return mVirtualDeviceId; - } - - public @Nullable String getMessage() { - return mMessage; - } - - public boolean getShouldCollectAsyncNotedOp() { - return mShouldCollectAsyncNotedOp; - } - - public boolean getShouldCollectMessage() { - return mShouldCollectMessage; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mOp); - dest.writeInt(mUid); - dest.writeString(mPackageName); - dest.writeString(mAttributionTag); - dest.writeInt(mVirtualDeviceId); - dest.writeString(mMessage); - dest.writeBoolean(mShouldCollectAsyncNotedOp); - dest.writeBoolean(mShouldCollectMessage); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NotedOp that = (NotedOp) o; - return mOp == that.mOp && mUid == that.mUid && Objects.equals(mPackageName, - that.mPackageName) && Objects.equals(mAttributionTag, that.mAttributionTag) - && mVirtualDeviceId == that.mVirtualDeviceId && Objects.equals(mMessage, - that.mMessage) && Objects.equals(mShouldCollectAsyncNotedOp, - that.mShouldCollectAsyncNotedOp) && Objects.equals(mShouldCollectMessage, - that.mShouldCollectMessage); - } - - @Override - public int hashCode() { - return Objects.hash(mOp, mUid, mPackageName, mAttributionTag, mVirtualDeviceId, - mMessage, mShouldCollectAsyncNotedOp, mShouldCollectMessage); - } - - @Override - public String toString() { - return "NotedOp{" + "mOp=" + mOp + ", mUid=" + mUid + ", mPackageName=" + mPackageName - + ", mAttributionTag=" + mAttributionTag + ", mVirtualDeviceId=" - + mVirtualDeviceId + ", mMessage=" + mMessage + ", mShouldCollectAsyncNotedOp=" - + mShouldCollectAsyncNotedOp + ", mShouldCollectMessage=" - + mShouldCollectMessage + "}"; - } - - - public static final @NonNull Creator<NotedOp> CREATOR = - new Creator<>() { - @Override public NotedOp createFromParcel(Parcel source) { - return new NotedOp(source); - } - - @Override public NotedOp[] newArray(int size) { - return new NotedOp[size]; - } - }; - } - - /** * Computes the sum of the counts for the given flags in between the begin and * end UID states. * @@ -8126,9 +7979,6 @@ public class AppOpsManager { AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; - mHandlerThread = new HandlerThread("AppOpsManager"); - mHandlerThread.start(); - mHandler = mHandlerThread.getThreadHandler(); if (mContext != null) { final PackageManager pm = mContext.getPackageManager(); @@ -9465,74 +9315,15 @@ public class AppOpsManager { } } - SyncNotedAppOp syncOp = null; - boolean skipBinderCall = false; - if (isNoteOpBatchingSupported()) { - int mode = sAppOpModeCache.query( - new AppOpModeQuery(op, uid, packageName, virtualDeviceId, attributionTag, - "noteOpNoThrow")); - // For FOREGROUND mode, we still need to make a binder call to the system service - // to translate it to ALLOWED or IGNORED. So no batching is needed. - if (mode != MODE_FOREGROUND) { - synchronized (mBatchedNoteOpLock) { - NotedOp notedOp = new NotedOp(op, uid, packageName, attributionTag, - virtualDeviceId, message, collectionMode == COLLECT_ASYNC, - shouldCollectMessage); - - // Batch same noteOp calls and send them with their counters to the system - // service asynchronously. The time window for batching is specified in - // NOTE_OP_BATCHING_DELAY_MILLIS. Always allow the first noteOp call to go - // through binder API. Accumulate subsequent same noteOp calls during the - // time window in mPendingNotedOps. - if (!mPendingNotedOps.containsKey(notedOp)) { - mPendingNotedOps.put(notedOp, 0); - } else { - skipBinderCall = true; - mPendingNotedOps.merge(notedOp, 1, Integer::sum); - } - - if (!mIsBatchedNoteOpCallScheduled) { - mHandler.postDelayed(() -> { - ArrayMap<NotedOp, Integer> pendingNotedOpsCopy; - synchronized(mBatchedNoteOpLock) { - mIsBatchedNoteOpCallScheduled = false; - pendingNotedOpsCopy = - new ArrayMap<NotedOp, Integer>(mPendingNotedOps); - mPendingNotedOps.clear(); - } - for (int i = pendingNotedOpsCopy.size() - 1; i >= 0; i--) { - if (pendingNotedOpsCopy.valueAt(i) == 0) { - pendingNotedOpsCopy.removeAt(i); - } - } - if (!pendingNotedOpsCopy.isEmpty()) { - try { - mService.noteOperationsInBatch(pendingNotedOpsCopy); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - }, NOTE_OP_BATCHING_DELAY_MILLIS); - - mIsBatchedNoteOpCallScheduled = true; - } - } - - syncOp = new SyncNotedAppOp(mode, op, attributionTag, packageName); - } - } - - if (!skipBinderCall) { - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - syncOp = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); - } else { - syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, - virtualDeviceId, collectionMode == COLLECT_ASYNC, message, - shouldCollectMessage); - } + SyncNotedAppOp syncOp; + if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { + syncOp = mService.noteOperation(op, uid, packageName, attributionTag, + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); + } else { + syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, + virtualDeviceId, collectionMode == COLLECT_ASYNC, message, + shouldCollectMessage); } - if (syncOp.getOpMode() == MODE_ALLOWED) { if (collectionMode == COLLECT_SELF) { collectNotedOpForSelf(syncOp); diff --git a/core/java/android/app/AppOpsManagerInternal.java b/core/java/android/app/AppOpsManagerInternal.java index 8b7ea0f8b46a..b21defbcc0e3 100644 --- a/core/java/android/app/AppOpsManagerInternal.java +++ b/core/java/android/app/AppOpsManagerInternal.java @@ -29,7 +29,7 @@ import com.android.internal.app.IAppOpsCallback; import com.android.internal.util.function.DodecFunction; import com.android.internal.util.function.HexConsumer; import com.android.internal.util.function.HexFunction; -import com.android.internal.util.function.NonaFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.UndecFunction; @@ -86,9 +86,9 @@ public abstract class AppOpsManagerInternal { */ SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, int notedCount, - @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String, - Boolean, Integer, SyncNotedAppOp> superImpl); + @Nullable String message, boolean shouldCollectMessage, + @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, + Boolean, SyncNotedAppOp> superImpl); /** * Allows overriding note proxy operation behavior. diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java index fe2e10752355..0b6cf59876d2 100644 --- a/core/java/android/app/BroadcastStickyCache.java +++ b/core/java/android/app/BroadcastStickyCache.java @@ -35,6 +35,7 @@ import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.view.WindowManagerPolicyConstants; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -71,8 +72,10 @@ public class BroadcastStickyCache { @VisibleForTesting public static final ArrayMap<String, String> sActionApiNameMap = new ArrayMap<>(); + @GuardedBy("BroadcastStickyCache.class") private static final ArrayMap<String, IpcDataCache.Config> sActionConfigMap = new ArrayMap<>(); + @GuardedBy("BroadcastStickyCache.class") private static final ArrayMap<StickyBroadcastFilter, IpcDataCache<Void, Intent>> sFilterCacheMap = new ArrayMap<>(); @@ -154,37 +157,44 @@ public class BroadcastStickyCache { @Nullable String broadcastPermission, @UserIdInt int userId, @RegisterReceiverFlags int flags) { - IpcDataCache<Void, Intent> intentDataCache = findIpcDataCache(filter); - - if (intentDataCache == null) { - final String action = filter.getAction(0); - final StickyBroadcastFilter stickyBroadcastFilter = - new StickyBroadcastFilter(filter, action); - final Config config = getConfig(action); - - intentDataCache = - new IpcDataCache<>(config, - (query) -> ActivityManager.getService().registerReceiverWithFeature( - applicationThread, - mBasePackageName, - attributionTag, - /* receiverId= */ "null", - /* receiver= */ null, - filter, - broadcastPermission, - userId, - flags)); - sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache); + IpcDataCache<Void, Intent> intentDataCache; + + synchronized (BroadcastStickyCache.class) { + intentDataCache = findIpcDataCache(filter); + + if (intentDataCache == null) { + final String action = filter.getAction(0); + final StickyBroadcastFilter stickyBroadcastFilter = + new StickyBroadcastFilter(filter, action); + final Config config = getConfig(action); + + intentDataCache = + new IpcDataCache<>(config, + (query) -> ActivityManager.getService().registerReceiverWithFeature( + applicationThread, + mBasePackageName, + attributionTag, + /* receiverId= */ "null", + /* receiver= */ null, + filter, + broadcastPermission, + userId, + flags)); + sFilterCacheMap.put(stickyBroadcastFilter, intentDataCache); + } } return intentDataCache.query(null); } @VisibleForTesting public static void clearCacheForTest() { - sFilterCacheMap.clear(); + synchronized (BroadcastStickyCache.class) { + sFilterCacheMap.clear(); + } } @Nullable + @GuardedBy("BroadcastStickyCache.class") private static IpcDataCache<Void, Intent> findIpcDataCache( @NonNull IntentFilter filter) { for (int i = sFilterCacheMap.size() - 1; i >= 0; i--) { @@ -198,6 +208,7 @@ public class BroadcastStickyCache { } @NonNull + @GuardedBy("BroadcastStickyCache.class") private static IpcDataCache.Config getConfig(@NonNull String action) { if (!sActionConfigMap.containsKey(action)) { // We only need 1 entry per cache but just to be on the safer side we are choosing 32 diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 06d01ecfcf06..063501bf82a2 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -46,6 +46,8 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.SharedMemory; +import android.os.instrumentation.IOffsetCallback; +import android.os.instrumentation.MethodDescriptor; import android.view.autofill.AutofillId; import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationSpec; @@ -183,4 +185,6 @@ oneway interface IApplicationThread { void scheduleTimeoutService(IBinder token, int startId); void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType); void schedulePing(in RemoteCallback pong); + void getExecutableMethodFileOffsets(in MethodDescriptor methodDescriptor, + in IOffsetCallback resultCallback); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a4d8a5cd4673..7e998d90e04f 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3221,7 +3221,6 @@ public class Notification implements Parcelable /** * @hide */ - @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) public boolean containsCustomViews() { return contentView != null || bigContentView != null @@ -3235,7 +3234,6 @@ public class Notification implements Parcelable /** * @hide */ - @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) public boolean hasTitle() { return extras != null && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE)) @@ -3245,7 +3243,7 @@ public class Notification implements Parcelable /** * @hide */ - @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) public boolean hasPromotableStyle() { final Class<? extends Style> notificationStyle = getNotificationStyle(); @@ -3257,11 +3255,16 @@ public class Notification implements Parcelable } /** - * @hide + * Returns whether the notification has all the characteristics that make it eligible for + * {@link #FLAG_PROMOTED_ONGOING}. This method does not factor in other criteria such user + * preferences for the app or channel. If this returns true, it does not guarantee that the + * notification will be assigned FLAG_PROMOTED_ONGOING by the system, but if this returns false, + * it will not. */ - @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) public boolean hasPromotableCharacteristics() { return isColorizedRequested() + && isOngoingEvent() && hasTitle() && !isGroupSummary() && !containsCustomViews() @@ -4158,6 +4161,13 @@ public class Notification implements Parcelable /** * @hide */ + public boolean isOngoingEvent() { + return (flags & FLAG_ONGOING_EVENT) != 0; + } + + /** + * @hide + */ public boolean hasCompletedProgress() { // not a progress notification; can't be complete if (!extras.containsKey(EXTRA_PROGRESS) diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 9bb0ba4cf8fa..1e971a5c736a 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -1294,6 +1294,13 @@ public class PropertyInvalidatedCache<Query, Result> { public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries, boolean mIsolateUids, boolean mTestMode, boolean mCacheNulls) { + /** + * Default values for fields. + */ + public static final int DEFAULT_MAX_ENTRIES = 32; + public static final boolean DEFAULT_ISOLATE_UIDS = true; + public static final boolean DEFAULT_CACHE_NULLS = false; + // Validation: the module must be one of the known module strings and the maxEntries must // be positive. public Args { @@ -1308,10 +1315,10 @@ public class PropertyInvalidatedCache<Query, Result> { public Args(@NonNull String module) { this(module, null, // api - 32, // maxEntries - true, // isolateUids + DEFAULT_MAX_ENTRIES, + DEFAULT_ISOLATE_UIDS, false, // testMode - true // allowNulls + DEFAULT_CACHE_NULLS ); } @@ -1361,7 +1368,7 @@ public class PropertyInvalidatedCache<Query, Result> { * Burst a property name into module and api. Throw if the key is invalid. This method is * used in to transition legacy cache constructors to the args constructor. */ - private static Args parseProperty(@NonNull String name) { + private static Args argsFromProperty(@NonNull String name) { throwIfInvalidCacheKey(name); // Strip off the leading well-known prefix. String base = name.substring(CACHE_KEY_PREFIX.length() + 1); @@ -1384,8 +1391,9 @@ public class PropertyInvalidatedCache<Query, Result> { * * @hide */ + @Deprecated public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) { - this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null); + this(argsFromProperty(propertyName).maxEntries(maxEntries), propertyName, null); } /** @@ -1399,9 +1407,10 @@ public class PropertyInvalidatedCache<Query, Result> { * @param cacheName Name of this cache in debug and dumpsys * @hide */ + @Deprecated public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName, @NonNull String cacheName) { - this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null); + this(argsFromProperty(propertyName).maxEntries(maxEntries), cacheName, null); } /** @@ -1857,6 +1866,14 @@ public class PropertyInvalidatedCache<Query, Result> { } /** + * Invalidate caches in all processes that have the module and api specified in the args. + * @hide + */ + public static void invalidateCache(@NonNull Args args) { + invalidateCache(createPropertyName(args.mModule, args.mApi)); + } + + /** * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on * {@var name}. This function is synchronous: caches are invalidated upon return. * diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f357836e9471..248e0433232a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -42,8 +42,7 @@ import android.app.contentsuggestions.IContentSuggestionsManager; import android.app.contextualsearch.ContextualSearchManager; import android.app.ecm.EnhancedConfirmationFrameworkInitializer; import android.app.job.JobSchedulerFrameworkInitializer; -import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager; -import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.OnDeviceIntelligenceFrameworkInitializer; import android.app.people.PeopleManager; import android.app.prediction.AppPredictionManager; import android.app.role.RoleFrameworkInitializer; @@ -1692,19 +1691,6 @@ public final class SystemServiceRegistry { throw new ServiceNotFoundException(Context.WEARABLE_SENSING_SERVICE); }}); - registerService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, OnDeviceIntelligenceManager.class, - new CachedServiceFetcher<OnDeviceIntelligenceManager>() { - @Override - public OnDeviceIntelligenceManager createService(ContextImpl ctx) - throws ServiceNotFoundException { - IBinder iBinder = ServiceManager.getServiceOrThrow( - Context.ON_DEVICE_INTELLIGENCE_SERVICE); - IOnDeviceIntelligenceManager manager = - IOnDeviceIntelligenceManager.Stub.asInterface(iBinder); - return new OnDeviceIntelligenceManager(ctx.getOuterContext(), manager); - } - }); - registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class, new CachedServiceFetcher<GrammaticalInflectionManager>() { @Override @@ -1849,6 +1835,7 @@ public final class SystemServiceRegistry { ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers(); NearbyFrameworkInitializer.registerServiceWrappers(); OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers(); + OnDeviceIntelligenceFrameworkInitializer.registerServiceWrappers(); DeviceLockFrameworkInitializer.registerServiceWrappers(); VirtualizationFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerBaklava.registerServiceWrappers(); diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 44940aee6a49..720e045dc944 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -53,16 +53,6 @@ flag { flag { namespace: "backstage_power" - name: "gate_fgs_timeout_anr_behavior" - description: "Gate the new behavior where an ANR is thrown once an FGS times out." - bug: "339315145" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "backstage_power" name: "enable_fgs_timeout_crash_behavior" description: "Enable the new behavior where the app is crashed once an FGS times out." bug: "339526947" @@ -166,3 +156,10 @@ flag { bug: "362537357" is_exported: true } + +flag { + name: "jank_perceptible_narrow" + namespace: "system_performance" + description: "Narrow the scope of Jank Perceptible" + bug: "304837972" +} diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java index c8d80d3afe43..d8179c7540d9 100644 --- a/core/java/android/app/appfunctions/AppFunctionException.java +++ b/core/java/android/app/appfunctions/AppFunctionException.java @@ -32,8 +32,8 @@ import java.util.Objects; /** * Represents an app function related error. * - * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle} - * containing additional error-specific metadata. + * <p>This exception may include an {@link AppFunctionException#getExtras() Bundle} containing + * additional error-specific metadata. * * <p>The AppFunction SDK can expose structured APIs by packing and unpacking this Bundle. */ @@ -85,6 +85,13 @@ public final class AppFunctionException extends Exception implements Parcelable public static final int ERROR_CANCELLED = 2001; /** + * The operation was disallowed by enterprise policy. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + + /** * An unknown error occurred while processing the call in the AppFunctionService. * * <p>This error is thrown when the service is connected in the remote application but an @@ -231,7 +238,8 @@ public final class AppFunctionException extends Exception implements Parcelable ERROR_SYSTEM_ERROR, ERROR_INVALID_ARGUMENT, ERROR_DISABLED, - ERROR_CANCELLED + ERROR_CANCELLED, + ERROR_ENTERPRISE_POLICY_DISALLOWED }) @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig index 529b59ac424d..e8cfd79c9cc7 100644 --- a/core/java/android/app/contextualsearch/flags.aconfig +++ b/core/java/android/app/contextualsearch/flags.aconfig @@ -17,14 +17,14 @@ flag { flag { name: "multi_window_screen_context" - namespace: "machine_learning" + namespace: "sysui_integrations" description: "Report screen context and positions for all windows." bug: "371065456" } flag { name: "contextual_search_window_layer" - namespace: "machine_learning" + namespace: "sysui_integrations" description: "Identify live contextual search UI to exclude from contextual search screenshot." bug: "372510690" }
\ No newline at end of file diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 7ceaeb3fb070..c9472598b352 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -215,7 +215,8 @@ public class JankDataProcessor { try { mPendingJankStats.values().forEach(stat -> { - FrameworkStatsLog.write(FrameworkStatsLog.JANK_FRAME_COUNT_BY_WIDGET, + FrameworkStatsLog.write( + FrameworkStatsLog.JANK_FRAME_COUNT_BY_WIDGET_REPORTED, /*app uid*/ stat.getUid(), /*activity name*/ stat.getActivityName(), /*widget id*/ stat.getWidgetId(), diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS deleted file mode 100644 index 85e9e653e6fb..000000000000 --- a/core/java/android/app/ondeviceintelligence/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Bug component: 1363385 - -sandeepbandaru@google.com -shivanker@google.com -hackz@google.com -volnov@google.com diff --git a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig b/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig deleted file mode 100644 index 74a96c864167..000000000000 --- a/core/java/android/app/ondeviceintelligence/flags/ondevice_intelligence.aconfig +++ /dev/null @@ -1,17 +0,0 @@ -package: "android.app.ondeviceintelligence.flags" -container: "system" - -flag { - name: "enable_on_device_intelligence" - is_exported: true - namespace: "ondeviceintelligence" - description: "Make methods on OnDeviceIntelligenceManager available for local inference." - bug: "304755128" -} -flag { - name: "enable_on_device_intelligence_module" - is_exported: true - namespace: "ondeviceintelligence" - description: "Enable migration to mainline module and related changes." - bug: "376427781" -}
\ No newline at end of file diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java index f66a1ae5c175..2f19eb49ad43 100644 --- a/core/java/android/companion/DeviceId.java +++ b/core/java/android/companion/DeviceId.java @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.net.MacAddress; import android.os.Parcel; import android.os.Parcelable; -import android.provider.OneTimeUseBuilder; import java.util.Locale; import java.util.Objects; @@ -154,8 +153,12 @@ public final class DeviceId implements Parcelable { /** * A builder for {@link DeviceId} + * + * <p>Calling apps must provide at least one of the following to identify + * the device: a custom ID using {@link #setCustomId(String)}, or a MAC address using + * {@link #setMacAddress(MacAddress)}.</p> */ - public static final class Builder extends OneTimeUseBuilder<DeviceId> { + public static final class Builder { private String mCustomId; private MacAddress mMacAddress; @@ -171,7 +174,6 @@ public final class DeviceId implements Parcelable { */ @NonNull public Builder setCustomId(@Nullable String customId) { - checkNotUsed(); if (customId != null && customId.length() > CUSTOM_ID_LENGTH_LIMIT) { throw new IllegalArgumentException("Length of the custom id must be at most " @@ -191,15 +193,12 @@ public final class DeviceId implements Parcelable { */ @NonNull public Builder setMacAddress(@Nullable MacAddress macAddress) { - checkNotUsed(); mMacAddress = macAddress; return this; } @NonNull - @Override public DeviceId build() { - markUsed(); if (mCustomId == null && mMacAddress == null) { throw new IllegalArgumentException("At least one device id property must be" + "non-null to build a DeviceId."); diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING index 44f2a4ca38e2..23f1ff8926df 100644 --- a/core/java/android/content/pm/TEST_MAPPING +++ b/core/java/android/content/pm/TEST_MAPPING @@ -158,6 +158,17 @@ ] }, { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJUpdateSelfTestCases", "options":[ { diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 68f17e2cd25a..d8d4e161006c 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -114,3 +114,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "credential_manager" + name: "propagate_user_context_for_intent_creation" + description: "Propagates the user ID in which to find the right OEM UI component to launch" + bug: "373711451" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java index b8af398a7594..77f937ebeabc 100644 --- a/core/java/android/hardware/contexthub/HubEndpointSession.java +++ b/core/java/android/hardware/contexthub/HubEndpointSession.java @@ -69,6 +69,8 @@ public class HubEndpointSession implements AutoCloseable { * @return For messages that does not require a response, the transaction will immediately * complete. For messages that requires a response, the transaction will complete after * receiving the response for the message. + * @throws SecurityException if the application doesn't have the right permissions to send this + * message. */ @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index eb7b409e77a6..aaa78aa0916a 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -189,4 +189,11 @@ flag { namespace: "wallet_integration" description: "Adds new API in WindowManager class to check if the window can override the power key double tap behavior." bug: "378736024" - }
\ No newline at end of file + } + +flag { + name: "pointer_acceleration" + namespace: "input" + description: "Allows the user to disable pointer acceleration for mouse and touchpads." + bug: "349006858" +}
\ No newline at end of file diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 310e1a64d84e..d9888ad6cd8d 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -841,6 +841,7 @@ public final class ContextHubManager { * @param endpointId The identifier of the hub endpoint. * @param callback The callback to be invoked. * @param executor The executor to invoke the callback on. + * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) @@ -881,6 +882,7 @@ public final class ContextHubManager { * @param callback The callback to be invoked. * @param executor The executor to invoke the callback on. * @throws IllegalArgumentException if the serviceDescriptor is empty. + * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) @@ -911,6 +913,7 @@ public final class ContextHubManager { * * @param callback The callback previously registered. * @throws IllegalArgumentException If the callback was not previously registered. + * @throws UnsupportedOperationException If the operation is not supported. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) @@ -1311,6 +1314,8 @@ public final class ContextHubManager { * endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}). * @param serviceDescriptor A string that describes the service associated with this session. * The information will be sent to the destination as part of open request. + * @throws IllegalStateException if hubEndpoint was not successfully registered, or if there is + * insufficient capacity for creating a session. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @FlaggedApi(Flags.FLAG_OFFLOAD_API) diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags index d5ed01496eba..32953c92d120 100644 --- a/core/java/android/net/EventLogTags.logtags +++ b/core/java/android/net/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.net diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 804e8faeef16..ce56a4f63a75 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -18,6 +18,8 @@ package android.os; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityThread; import android.app.Instrumentation; @@ -27,11 +29,14 @@ import android.os.UserHandle; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodRedirect; import android.ravenwood.annotation.RavenwoodRedirectionClass; +import android.ravenwood.annotation.RavenwoodReplace; +import android.ravenwood.annotation.RavenwoodThrow; import android.util.Log; import android.util.Printer; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.NeverCompile; @@ -174,6 +179,28 @@ public final class MessageQueue { } } + @RavenwoodReplace + private static void throwIfNotTest() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + // Only tests can reach here. + return; + } + final Instrumentation instrumentation = activityThread.getInstrumentation(); + if (instrumentation == null) { + // Only tests can reach here. + return; + } + if (instrumentation.isInstrumenting()) { + return; + } + throw new IllegalStateException("Test-only API called not from a test!"); + } + + private static void throwIfNotTest$ravenwood() { + return; + } + private static boolean isInstrumenting() { final ActivityThread activityThread = ActivityThread.currentActivityThread(); if (activityThread == null) { @@ -203,12 +230,9 @@ public final class MessageQueue { private static final class MatchDeliverableMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { - if (m.when <= when) { - return true; - } - return false; + return n.mMessage.when <= when; } } private final MatchDeliverableMessages mMatchDeliverableMessages = @@ -355,7 +379,7 @@ public final class MessageQueue { * @see OnFileDescriptorEventListener * @see #removeOnFileDescriptorEventListener */ - @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { @@ -389,7 +413,7 @@ public final class MessageQueue { * @see OnFileDescriptorEventListener * @see #addOnFileDescriptorEventListener */ - @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) { if (fd == null) { throw new IllegalArgumentException("fd must not be null"); @@ -405,7 +429,7 @@ public final class MessageQueue { } } - @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + @RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, OnFileDescriptorEventListener listener) { final int fdNum = fd.getInt$(); @@ -528,7 +552,7 @@ public final class MessageQueue { /* This is only read/written from the Looper thread. For use with Concurrent MQ */ private int mNextPollTimeoutMillis; private boolean mMessageDirectlyQueued; - private Message nextMessage() { + private Message nextMessage(boolean peek) { int i = 0; while (true) { @@ -690,7 +714,7 @@ public final class MessageQueue { if (sState.compareAndSet(this, sStackStateActive, nextOp)) { mMessageCounts.clearCounts(); if (found != null) { - if (!removeFromPriorityQueue(found)) { + if (!peek && !removeFromPriorityQueue(found)) { /* * RemoveMessages() might be able to pull messages out from under us * However we can detect that here and just loop around if it happens. @@ -724,7 +748,7 @@ public final class MessageQueue { mMessageDirectlyQueued = false; nativePollOnce(ptr, mNextPollTimeoutMillis); - Message msg = nextMessage(); + Message msg = nextMessage(false); if (msg != null) { msg.markInUse(); return msg; @@ -1043,8 +1067,9 @@ public final class MessageQueue { } @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == null && m.arg1 == mBarrierToken) { return true; } @@ -1247,10 +1272,155 @@ public final class MessageQueue { return true; } + private Message legacyPeekOrPop(boolean peek) { + synchronized (this) { + // Try to retrieve the next message. Return if found. + final long now = SystemClock.uptimeMillis(); + Message prevMsg = null; + Message msg = mMessages; + if (msg != null && msg.target == null) { + // Stalled by a barrier. Find the next asynchronous message in the queue. + do { + prevMsg = msg; + msg = msg.next; + } while (msg != null && !msg.isAsynchronous()); + } + if (msg != null) { + if (now >= msg.when) { + // Got a message. + mBlocked = false; + if (peek) { + return msg; + } + if (prevMsg != null) { + prevMsg.next = msg.next; + if (prevMsg.next == null) { + mLast = prevMsg; + } + } else { + mMessages = msg.next; + if (msg.next == null) { + mLast = null; + } + } + msg.next = null; + msg.markInUse(); + if (msg.isAsynchronous()) { + mAsyncMessageCount--; + } + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return msg; + } + } + } + return null; + } + + /** + * Get the timestamp of the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery. + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + Long peekWhenForTest() { + throwIfNotTest(); + Message ret; + if (mUseConcurrent) { + ret = nextMessage(true); + } else { + ret = legacyPeekOrPop(true); + } + return ret != null ? ret.when : null; + } + + /** + * Return the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + @Nullable + Message popForTest() { + throwIfNotTest(); + if (mUseConcurrent) { + return nextMessage(false); + } else { + return legacyPeekOrPop(false); + } + } + + /** + * @return true if we are blocked on a sync barrier + */ + boolean isBlockedOnSyncBarrier() { + throwIfNotTest(); + if (mUseConcurrent) { + Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); + MessageNode queueNode = iterateNext(queueIter); + + if (queueNode.isBarrier()) { + long now = SystemClock.uptimeMillis(); + + /* Look for a deliverable async node. If one exists we are not blocked. */ + Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator(); + MessageNode asyncNode = iterateNext(asyncQueueIter); + if (asyncNode != null && now >= asyncNode.getWhen()) { + return false; + } + /* + * Look for a deliverable sync node. In this case, if one exists we are blocked + * since the barrier prevents delivery of the Message. + */ + while (queueNode.isBarrier()) { + queueNode = iterateNext(queueIter); + } + if (queueNode != null && now >= queueNode.getWhen()) { + return true; + } + + return false; + } + } else { + Message msg = mMessages; + if (msg != null && msg.target == null) { + Message iter = msg; + /* Look for a deliverable async node */ + do { + iter = iter.next; + } while (iter != null && !iter.isAsynchronous()); + + long now = SystemClock.uptimeMillis(); + if (iter != null && now >= iter.when) { + return false; + } + /* + * Look for a deliverable sync node. In this case, if one exists we are blocked + * since the barrier prevents delivery of the Message. + */ + iter = msg; + do { + iter = iter.next; + } while (iter != null && (iter.target == null || iter.isAsynchronous())); + + if (iter != null && now >= iter.when) { + return true; + } + return false; + } + } + /* No barrier was found. */ + return false; + } + private static final class MatchHandlerWhatAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.what == what && (object == null || m.obj == object)) { return true; } @@ -1281,8 +1451,9 @@ public final class MessageQueue { private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) { return true; } @@ -1314,8 +1485,9 @@ public final class MessageQueue { private static final class MatchHandlerRunnableAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.callback == r && (object == null || m.obj == object)) { return true; } @@ -1348,12 +1520,9 @@ public final class MessageQueue { private static final class MatchHandler extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { - if (m.target == h) { - return true; - } - return false; + return n.mMessage.target == h; } } private final MatchHandler mMatchHandler = new MatchHandler(); @@ -1531,8 +1700,9 @@ public final class MessageQueue { private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) { return true; } @@ -1594,8 +1764,9 @@ public final class MessageQueue { private static final class MatchHandlerAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && (object == null || m.obj == object)) { return true; } @@ -1655,8 +1826,9 @@ public final class MessageQueue { private static final class MatchHandlerAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && (object == null || object.equals(m.obj))) { return true; } @@ -1762,7 +1934,7 @@ public final class MessageQueue { private static final class MatchAllMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { return true; } @@ -1774,9 +1946,10 @@ public final class MessageQueue { private static final class MatchAllFutureMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { - if (m.when > when) { + final Message m = n.mMessage; + if (m.when > when) { return true; } return false; @@ -2482,7 +2655,7 @@ public final class MessageQueue { * This class is used to find matches for hasMessages() and removeMessages() */ private abstract static class MessageCompare { - public abstract boolean compareMessage(Message m, Handler h, int what, Object object, + public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when); } @@ -2517,7 +2690,7 @@ public final class MessageQueue { MessageNode p = (MessageNode) top; while (true) { - if (compare.compareMessage(p.mMessage, h, what, object, r, when)) { + if (compare.compareMessage(p, h, what, object, r, when)) { found = true; if (DEBUG) { Log.d(TAG_C, "stackHasMessages node matches"); @@ -2562,7 +2735,7 @@ public final class MessageQueue { while (iterator.hasNext()) { MessageNode msg = iterator.next(); - if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) { + if (compare.compareMessage(msg, h, what, object, r, when)) { if (removeMatches) { found = true; if (queue.remove(msg)) { diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java index c2a47d767801..576c4cc5b442 100644 --- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -16,9 +16,12 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; +import android.app.ActivityThread; +import android.app.Instrumentation; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodRedirect; import android.ravenwood.annotation.RavenwoodRedirectionClass; @@ -364,6 +367,28 @@ public final class MessageQueue { mPtr = nativeInit(); } + @android.ravenwood.annotation.RavenwoodReplace + private static void throwIfNotTest() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + // Only tests can reach here. + return; + } + final Instrumentation instrumentation = activityThread.getInstrumentation(); + if (instrumentation == null) { + // Only tests can reach here. + return; + } + if (instrumentation.isInstrumenting()) { + return; + } + throw new IllegalStateException("Test-only API called not from a test!"); + } + + private static void throwIfNotTest$ravenwood() { + return; + } + @Override protected void finalize() throws Throwable { try { @@ -384,8 +409,9 @@ public final class MessageQueue { private static final class MatchDeliverableMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.when <= when) { return true; } @@ -562,7 +588,7 @@ public final class MessageQueue { private static final AtomicLong mMessagesDelivered = new AtomicLong(); private boolean mMessageDirectlyQueued; - private Message nextMessage() { + private Message nextMessage(boolean peek) { int i = 0; while (true) { @@ -724,7 +750,7 @@ public final class MessageQueue { if (sState.compareAndSet(this, sStackStateActive, nextOp)) { mMessageCounts.clearCounts(); if (found != null) { - if (!removeFromPriorityQueue(found)) { + if (!peek && !removeFromPriorityQueue(found)) { /* * RemoveMessages() might be able to pull messages out from under us * However we can detect that here and just loop around if it happens. @@ -993,8 +1019,9 @@ public final class MessageQueue { } @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == null && m.arg1 == mBarrierToken) { return true; } @@ -1039,6 +1066,79 @@ public final class MessageQueue { } } + private static final class MatchEarliestMessage extends MessageCompare { + MessageNode mEarliest = null; + + @Override + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; + if (mEarliest == null || mEarliest.mMessage.when > m.when) { + mEarliest = n; + } + + return false; + } + } + + /** + * Get the timestamp of the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery. + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + Long peekWhenForTest() { + throwIfNotTest(); + Message ret = nextMessage(true); + return ret != null ? ret.when : null; + } + + /** + * Return the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + @Nullable + Message popForTest() { + throwIfNotTest(); + return nextMessage(false); + } + + /** + * @return true if we are blocked on a sync barrier + */ + boolean isBlockedOnSyncBarrier() { + throwIfNotTest(); + Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); + MessageNode queueNode = iterateNext(queueIter); + + if (queueNode.isBarrier()) { + long now = SystemClock.uptimeMillis(); + + /* Look for a deliverable async node. If one exists we are not blocked. */ + Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator(); + MessageNode asyncNode = iterateNext(asyncQueueIter); + if (asyncNode != null && now >= asyncNode.getWhen()) { + return false; + } + /* + * Look for a deliverable sync node. In this case, if one exists we are blocked + * since the barrier prevents delivery of the Message. + */ + while (queueNode.isBarrier()) { + queueNode = iterateNext(queueIter); + } + if (queueNode != null && now >= queueNode.getWhen()) { + return true; + } + + return false; + } + } + private StateNode getStateNode(StackNode node) { if (node.isMessageNode()) { return ((MessageNode) node).mBottomOfStack; @@ -1058,7 +1158,7 @@ public final class MessageQueue { * This class is used to find matches for hasMessages() and removeMessages() */ private abstract static class MessageCompare { - public abstract boolean compareMessage(Message m, Handler h, int what, Object object, + public abstract boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when); } @@ -1167,8 +1267,9 @@ public final class MessageQueue { private static final class MatchHandlerWhatAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.what == what && (object == null || m.obj == object)) { return true; } @@ -1187,8 +1288,9 @@ public final class MessageQueue { private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) { return true; } @@ -1208,8 +1310,9 @@ public final class MessageQueue { private static final class MatchHandlerRunnableAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.callback == r && (object == null || m.obj == object)) { return true; } @@ -1229,8 +1332,9 @@ public final class MessageQueue { private static final class MatchHandler extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h) { return true; } @@ -1268,8 +1372,9 @@ public final class MessageQueue { private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) { return true; } @@ -1287,8 +1392,9 @@ public final class MessageQueue { private static final class MatchHandlerAndObject extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && (object == null || m.obj == object)) { return true; } @@ -1305,8 +1411,9 @@ public final class MessageQueue { private static final class MatchHandlerAndObjectEquals extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.target == h && (object == null || object.equals(m.obj))) { return true; } @@ -1324,8 +1431,8 @@ public final class MessageQueue { private static final class MatchAllMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { return true; } } @@ -1336,8 +1443,9 @@ public final class MessageQueue { private static final class MatchAllFutureMessages extends MessageCompare { @Override - public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, - long when) { + public boolean compareMessage(MessageNode n, Handler h, int what, Object object, + Runnable r, long when) { + final Message m = n.mMessage; if (m.when > when) { return true; } diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index 8db1567336d3..a2e9314f6436 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -400,10 +400,11 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, } /** - * This is a convenience class that encapsulates configuration information for a - * cache. It may be supplied to the cache constructors in lieu of the other - * parameters. The class captures maximum entry count, the module, the key, and the - * api. + * This is a convenience class that encapsulates configuration information for a cache. It + * may be supplied to the cache constructors in lieu of the other parameters. The class + * captures maximum entry count, the module, the key, and the api. The key is used to + * invalidate the cache and may be shared by different caches. The api is a user-visible (in + * debug) name for the cache. * * There are three specific use cases supported by this class. * @@ -430,11 +431,8 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * @hide */ public static class Config { - private final int mMaxEntries; - @IpcDataCacheModule - private final String mModule; - private final String mApi; - private final String mName; + final Args mArgs; + final String mName; /** * The list of cache names that were created extending this Config. If @@ -452,12 +450,20 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, */ private boolean mDisabled = false; + /** + * Fully construct a config. + */ + private Config(@NonNull Args args, @NonNull String name) { + mArgs = args; + mName = name; + } + + /** + * + */ public Config(int maxEntries, @NonNull @IpcDataCacheModule String module, @NonNull String api, @NonNull String name) { - mMaxEntries = maxEntries; - mModule = module; - mApi = api; - mName = name; + this(new Args(module).api(api).maxEntries(maxEntries), name); } /** @@ -473,7 +479,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * the parameter list. */ public Config(@NonNull Config root, @NonNull String api, @NonNull String name) { - this(root.maxEntries(), root.module(), api, name); + this(root.mArgs.api(api), name); } /** @@ -481,7 +487,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * the parameter list. */ public Config(@NonNull Config root, @NonNull String api) { - this(root.maxEntries(), root.module(), api, api); + this(root.mArgs.api(api), api); } /** @@ -490,26 +496,23 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * current process. */ public Config child(@NonNull String name) { - final Config result = new Config(this, api(), name); + final Config result = new Config(mArgs, name); registerChild(name); return result; } - public final int maxEntries() { - return mMaxEntries; - } - - @IpcDataCacheModule - public final @NonNull String module() { - return mModule; - } - - public final @NonNull String api() { - return mApi; + /** + * Set the cacheNull behavior. + */ + public Config cacheNulls(boolean enable) { + return new Config(mArgs.cacheNulls(enable), mName); } - public final @NonNull String name() { - return mName; + /** + * Set the isolateUidss behavior. + */ + public Config isolateUids(boolean enable) { + return new Config(mArgs.isolateUids(enable), mName); } /** @@ -532,7 +535,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * Invalidate all caches that share this Config's module and api. */ public void invalidateCache() { - IpcDataCache.invalidateCache(mModule, mApi); + IpcDataCache.invalidateCache(mArgs); } /** @@ -564,8 +567,7 @@ public class IpcDataCache<Query, Result> extends PropertyInvalidatedCache<Query, * @hide */ public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) { - super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()), - config.name(), computer); + super(config.mArgs, config.mName, computer); } /** diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index cae82d010132..10d090444c59 100644 --- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -16,9 +16,14 @@ package android.os; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; +import android.app.ActivityThread; +import android.app.Instrumentation; import android.compat.annotation.UnsupportedAppUsage; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.ravenwood.annotation.RavenwoodRedirect; @@ -99,6 +104,28 @@ public final class MessageQueue { mPtr = nativeInit(); } + @android.ravenwood.annotation.RavenwoodReplace + private static void throwIfNotTest() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread == null) { + // Only tests can reach here. + return; + } + final Instrumentation instrumentation = activityThread.getInstrumentation(); + if (instrumentation == null) { + // Only tests can reach here. + return; + } + if (instrumentation.isInstrumenting()) { + return; + } + throw new IllegalStateException("Test-only API called not from a test!"); + } + + private static void throwIfNotTest$ravenwood() { + return; + } + @Override protected void finalize() throws Throwable { try { @@ -713,6 +740,112 @@ public final class MessageQueue { return true; } + private Message legacyPeekOrPop(boolean peek) { + synchronized (this) { + // Try to retrieve the next message. Return if found. + final long now = SystemClock.uptimeMillis(); + Message prevMsg = null; + Message msg = mMessages; + if (msg != null && msg.target == null) { + // Stalled by a barrier. Find the next asynchronous message in the queue. + do { + prevMsg = msg; + msg = msg.next; + } while (msg != null && !msg.isAsynchronous()); + } + if (msg != null) { + if (now >= msg.when) { + // Got a message. + mBlocked = false; + if (peek) { + return msg; + } + if (prevMsg != null) { + prevMsg.next = msg.next; + if (prevMsg.next == null) { + mLast = prevMsg; + } + } else { + mMessages = msg.next; + if (msg.next == null) { + mLast = null; + } + } + msg.next = null; + msg.markInUse(); + if (msg.isAsynchronous()) { + mAsyncMessageCount--; + } + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return msg; + } + } + } + return null; + } + + /** + * Get the timestamp of the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery. + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + Long peekWhenForTest() { + throwIfNotTest(); + Message ret = legacyPeekOrPop(true); + return ret != null ? ret.when : null; + } + + /** + * Return the next executable message in our priority queue. + * Returns null if there are no messages ready for delivery + * + * Caller must ensure that this doesn't race 'next' from the Looper thread. + */ + @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this + @Nullable + Message popForTest() { + throwIfNotTest(); + return legacyPeekOrPop(false); + } + + /** + * @return true if we are blocked on a sync barrier + */ + boolean isBlockedOnSyncBarrier() { + throwIfNotTest(); + Message msg = mMessages; + if (msg != null && msg.target == null) { + Message iter = msg; + /* Look for a deliverable async node */ + do { + iter = iter.next; + } while (iter != null && !iter.isAsynchronous()); + + long now = SystemClock.uptimeMillis(); + if (iter != null && now >= iter.when) { + return false; + } + /* + * Look for a deliverable sync node. In this case, if one exists we are blocked + * since the barrier prevents delivery of the Message. + */ + iter = msg; + do { + iter = iter.next; + } while (iter != null && (iter.target == null || iter.isAsynchronous())); + + if (iter != null && now >= iter.when) { + return true; + } + return false; + } + return false; + } + boolean hasMessages(Handler h, int what, Object object) { if (h == null) { return false; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6855d95d718f..cf473ec9c3ea 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -50,7 +50,6 @@ import com.android.internal.util.ArrayUtils; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; -import dalvik.annotation.optimization.NeverInline; import libcore.util.SneakyThrow; @@ -588,17 +587,6 @@ public final class Parcel { return parcel; } - @NeverInline - private void errorUsedWhileRecycling() { - Log.wtf(TAG, "Parcel used while recycled. " - + Log.getStackTraceString(new Throwable()) - + " Original recycle call (if DEBUG_RECYCLE): ", mStack); - } - - private void assertNotRecycled() { - if (mRecycled) errorUsedWhileRecycling(); - } - /** * Put a Parcel object back into the pool. You must not touch * the object after this call. @@ -647,7 +635,6 @@ public final class Parcel { * @hide */ public void setReadWriteHelper(@Nullable ReadWriteHelper helper) { - assertNotRecycled(); mReadWriteHelper = helper != null ? helper : ReadWriteHelper.DEFAULT; } @@ -657,7 +644,6 @@ public final class Parcel { * @hide */ public boolean hasReadWriteHelper() { - assertNotRecycled(); return (mReadWriteHelper != null) && (mReadWriteHelper != ReadWriteHelper.DEFAULT); } @@ -684,7 +670,6 @@ public final class Parcel { * @hide */ public final void markSensitive() { - assertNotRecycled(); nativeMarkSensitive(mNativePtr); } @@ -701,7 +686,6 @@ public final class Parcel { * @hide */ public final boolean isForRpc() { - assertNotRecycled(); return nativeIsForRpc(mNativePtr); } @@ -709,25 +693,21 @@ public final class Parcel { @ParcelFlags @TestApi public int getFlags() { - assertNotRecycled(); return mFlags; } /** @hide */ public void setFlags(@ParcelFlags int flags) { - assertNotRecycled(); mFlags = flags; } /** @hide */ public void addFlags(@ParcelFlags int flags) { - assertNotRecycled(); mFlags |= flags; } /** @hide */ private boolean hasFlags(@ParcelFlags int flags) { - assertNotRecycled(); return (mFlags & flags) == flags; } @@ -740,7 +720,6 @@ public final class Parcel { // We don't really need to protect it; even if 3p / non-system apps, nothing would happen. // This would only work when used on a reply parcel by a binder object that's allowed-blocking. public void setPropagateAllowBlocking() { - assertNotRecycled(); addFlags(FLAG_PROPAGATE_ALLOW_BLOCKING); } @@ -748,7 +727,6 @@ public final class Parcel { * Returns the total amount of data contained in the parcel. */ public int dataSize() { - assertNotRecycled(); return nativeDataSize(mNativePtr); } @@ -757,7 +735,6 @@ public final class Parcel { * parcel. That is, {@link #dataSize}-{@link #dataPosition}. */ public final int dataAvail() { - assertNotRecycled(); return nativeDataAvail(mNativePtr); } @@ -766,7 +743,6 @@ public final class Parcel { * more than {@link #dataSize}. */ public final int dataPosition() { - assertNotRecycled(); return nativeDataPosition(mNativePtr); } @@ -777,7 +753,6 @@ public final class Parcel { * data buffer. */ public final int dataCapacity() { - assertNotRecycled(); return nativeDataCapacity(mNativePtr); } @@ -789,7 +764,6 @@ public final class Parcel { * @param size The new number of bytes in the Parcel. */ public final void setDataSize(int size) { - assertNotRecycled(); nativeSetDataSize(mNativePtr, size); } @@ -799,7 +773,6 @@ public final class Parcel { * {@link #dataSize}. */ public final void setDataPosition(int pos) { - assertNotRecycled(); nativeSetDataPosition(mNativePtr, pos); } @@ -811,13 +784,11 @@ public final class Parcel { * with this method. */ public final void setDataCapacity(int size) { - assertNotRecycled(); nativeSetDataCapacity(mNativePtr, size); } /** @hide */ public final boolean pushAllowFds(boolean allowFds) { - assertNotRecycled(); return nativePushAllowFds(mNativePtr, allowFds); } @@ -838,7 +809,6 @@ public final class Parcel { * in different versions of the platform. */ public final byte[] marshall() { - assertNotRecycled(); return nativeMarshall(mNativePtr); } @@ -846,18 +816,15 @@ public final class Parcel { * Fills the raw bytes of this Parcel with the supplied data. */ public final void unmarshall(@NonNull byte[] data, int offset, int length) { - assertNotRecycled(); nativeUnmarshall(mNativePtr, data, offset, length); } public final void appendFrom(Parcel parcel, int offset, int length) { - assertNotRecycled(); nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length); } /** @hide */ public int compareData(Parcel other) { - assertNotRecycled(); return nativeCompareData(mNativePtr, other.mNativePtr); } @@ -868,7 +835,6 @@ public final class Parcel { /** @hide */ public final void setClassCookie(Class clz, Object cookie) { - assertNotRecycled(); if (mClassCookies == null) { mClassCookies = new ArrayMap<>(); } @@ -878,13 +844,11 @@ public final class Parcel { /** @hide */ @Nullable public final Object getClassCookie(Class clz) { - assertNotRecycled(); return mClassCookies != null ? mClassCookies.get(clz) : null; } /** @hide */ public void removeClassCookie(Class clz, Object expectedCookie) { - assertNotRecycled(); if (mClassCookies != null) { Object removedCookie = mClassCookies.remove(clz); if (removedCookie != expectedCookie) { @@ -902,25 +866,21 @@ public final class Parcel { * @hide */ public boolean hasClassCookie(Class clz) { - assertNotRecycled(); return mClassCookies != null && mClassCookies.containsKey(clz); } /** @hide */ public final void adoptClassCookies(Parcel from) { - assertNotRecycled(); mClassCookies = from.mClassCookies; } /** @hide */ public Map<Class, Object> copyClassCookies() { - assertNotRecycled(); return new ArrayMap<>(mClassCookies); } /** @hide */ public void putClassCookies(Map<Class, Object> cookies) { - assertNotRecycled(); if (cookies == null) { return; } @@ -932,9 +892,14 @@ public final class Parcel { /** * Report whether the parcel contains any marshalled file descriptors. + * + * WARNING: Parcelable definitions change over time. Unless you define + * a Parcelable yourself OR the Parcelable explicitly guarantees that + * it would never include such objects, you should not expect the return + * value to stay the same, and your code should continue to work even + * if the return value changes. */ public boolean hasFileDescriptors() { - assertNotRecycled(); return nativeHasFileDescriptors(mNativePtr); } @@ -942,6 +907,12 @@ public final class Parcel { * Report whether the parcel contains any marshalled file descriptors in the range defined by * {@code offset} and {@code length}. * + * WARNING: Parcelable definitions change over time. Unless you define + * a Parcelable yourself OR the Parcelable explicitly guarantees that + * it would never include such objects, you should not expect the return + * value to stay the same, and your code should continue to work even + * if the return value changes. + * * @param offset The offset from which the range starts. Should be between 0 and * {@link #dataSize()}. * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code @@ -950,7 +921,6 @@ public final class Parcel { * @throws IllegalArgumentException if the parameters are out of the permitted ranges. */ public boolean hasFileDescriptors(int offset, int length) { - assertNotRecycled(); return nativeHasFileDescriptorsInRange(mNativePtr, offset, length); } @@ -963,6 +933,12 @@ public final class Parcel { * <p>For most cases, it will use the self-reported {@link Parcelable#describeContents()} method * for that. * + * WARNING: Parcelable definitions change over time. Unless you define + * a Parcelable yourself OR the Parcelable explicitly guarantees that + * it would never include such objects, you should not expect the return + * value to stay the same, and your code should continue to work even + * if the return value changes. + * * @throws IllegalArgumentException if you provide any object not supported by above methods * (including if the unsupported object is inside a nested container). * @@ -1032,10 +1008,16 @@ public final class Parcel { * * @throws UnsupportedOperationException if binder kernel driver was disabled or if method was * invoked in case of Binder RPC protocol. + * + * WARNING: Parcelable definitions change over time. Unless you define + * a Parcelable yourself OR the Parcelable explicitly guarantees that + * it would never include such objects, you should not expect the return + * value to stay the same, and your code should continue to work even + * if the return value changes. + * * @hide */ public boolean hasBinders() { - assertNotRecycled(); return nativeHasBinders(mNativePtr); } @@ -1043,6 +1025,12 @@ public final class Parcel { * Report whether the parcel contains any marshalled {@link IBinder} objects in the range * defined by {@code offset} and {@code length}. * + * WARNING: Parcelable definitions change over time. Unless you define + * a Parcelable yourself OR the Parcelable explicitly guarantees that + * it would never include such objects, you should not expect the return + * value to stay the same, and your code should continue to work even + * if the return value changes. + * * @param offset The offset from which the range starts. Should be between 0 and * {@link #dataSize()}. * @param length The length of the range. Should be between 0 and {@link #dataSize()} - {@code @@ -1053,7 +1041,6 @@ public final class Parcel { * @hide */ public boolean hasBinders(int offset, int length) { - assertNotRecycled(); return nativeHasBindersInRange(mNativePtr, offset, length); } @@ -1064,7 +1051,6 @@ public final class Parcel { * at the beginning of transactions as a header. */ public final void writeInterfaceToken(@NonNull String interfaceName) { - assertNotRecycled(); nativeWriteInterfaceToken(mNativePtr, interfaceName); } @@ -1075,7 +1061,6 @@ public final class Parcel { * should propagate to the caller. */ public final void enforceInterface(@NonNull String interfaceName) { - assertNotRecycled(); nativeEnforceInterface(mNativePtr, interfaceName); } @@ -1086,7 +1071,6 @@ public final class Parcel { * When used over binder, this exception should propagate to the caller. */ public void enforceNoDataAvail() { - assertNotRecycled(); final int n = dataAvail(); if (n > 0) { throw new BadParcelableException("Parcel data not fully consumed, unread size: " + n); @@ -1103,7 +1087,6 @@ public final class Parcel { * @hide */ public boolean replaceCallingWorkSourceUid(int workSourceUid) { - assertNotRecycled(); return nativeReplaceCallingWorkSourceUid(mNativePtr, workSourceUid); } @@ -1120,7 +1103,6 @@ public final class Parcel { * @hide */ public int readCallingWorkSourceUid() { - assertNotRecycled(); return nativeReadCallingWorkSourceUid(mNativePtr); } @@ -1130,7 +1112,6 @@ public final class Parcel { * @param b Bytes to place into the parcel. */ public final void writeByteArray(@Nullable byte[] b) { - assertNotRecycled(); writeByteArray(b, 0, (b != null) ? b.length : 0); } @@ -1142,7 +1123,6 @@ public final class Parcel { * @param len Number of bytes to write. */ public final void writeByteArray(@Nullable byte[] b, int offset, int len) { - assertNotRecycled(); if (b == null) { writeInt(-1); return; @@ -1164,7 +1144,6 @@ public final class Parcel { * @see #readBlob() */ public final void writeBlob(@Nullable byte[] b) { - assertNotRecycled(); writeBlob(b, 0, (b != null) ? b.length : 0); } @@ -1183,7 +1162,6 @@ public final class Parcel { * @see #readBlob() */ public final void writeBlob(@Nullable byte[] b, int offset, int len) { - assertNotRecycled(); if (b == null) { writeInt(-1); return; @@ -1202,7 +1180,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeInt(int val) { - assertNotRecycled(); int err = nativeWriteInt(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1214,7 +1191,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeLong(long val) { - assertNotRecycled(); int err = nativeWriteLong(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1226,7 +1202,6 @@ public final class Parcel { * dataPosition(), growing dataCapacity() if needed. */ public final void writeFloat(float val) { - assertNotRecycled(); int err = nativeWriteFloat(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1238,7 +1213,6 @@ public final class Parcel { * current dataPosition(), growing dataCapacity() if needed. */ public final void writeDouble(double val) { - assertNotRecycled(); int err = nativeWriteDouble(mNativePtr, val); if (err != OK) { nativeSignalExceptionForError(err); @@ -1250,19 +1224,16 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeString(@Nullable String val) { - assertNotRecycled(); writeString16(val); } /** {@hide} */ public final void writeString8(@Nullable String val) { - assertNotRecycled(); mReadWriteHelper.writeString8(this, val); } /** {@hide} */ public final void writeString16(@Nullable String val) { - assertNotRecycled(); mReadWriteHelper.writeString16(this, val); } @@ -1274,19 +1245,16 @@ public final class Parcel { * @hide */ public void writeStringNoHelper(@Nullable String val) { - assertNotRecycled(); writeString16NoHelper(val); } /** {@hide} */ public void writeString8NoHelper(@Nullable String val) { - assertNotRecycled(); nativeWriteString8(mNativePtr, val); } /** {@hide} */ public void writeString16NoHelper(@Nullable String val) { - assertNotRecycled(); nativeWriteString16(mNativePtr, val); } @@ -1298,7 +1266,6 @@ public final class Parcel { * for true or false, respectively, but may change in the future. */ public final void writeBoolean(boolean val) { - assertNotRecycled(); writeInt(val ? 1 : 0); } @@ -1310,7 +1277,6 @@ public final class Parcel { @UnsupportedAppUsage @RavenwoodThrow(blockedBy = android.text.Spanned.class) public final void writeCharSequence(@Nullable CharSequence val) { - assertNotRecycled(); TextUtils.writeToParcel(val, this, 0); } @@ -1319,7 +1285,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeStrongBinder(IBinder val) { - assertNotRecycled(); nativeWriteStrongBinder(mNativePtr, val); } @@ -1328,7 +1293,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeStrongInterface(IInterface val) { - assertNotRecycled(); writeStrongBinder(val == null ? null : val.asBinder()); } @@ -1343,7 +1307,6 @@ public final class Parcel { * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p> */ public final void writeFileDescriptor(@NonNull FileDescriptor val) { - assertNotRecycled(); nativeWriteFileDescriptor(mNativePtr, val); } @@ -1352,7 +1315,6 @@ public final class Parcel { * This will be the new name for writeFileDescriptor, for consistency. **/ public final void writeRawFileDescriptor(@NonNull FileDescriptor val) { - assertNotRecycled(); nativeWriteFileDescriptor(mNativePtr, val); } @@ -1363,7 +1325,6 @@ public final class Parcel { * @param value The array of objects to be written. */ public final void writeRawFileDescriptorArray(@Nullable FileDescriptor[] value) { - assertNotRecycled(); if (value != null) { int N = value.length; writeInt(N); @@ -1383,7 +1344,6 @@ public final class Parcel { * the future. */ public final void writeByte(byte val) { - assertNotRecycled(); writeInt(val); } @@ -1399,7 +1359,6 @@ public final class Parcel { * allows you to avoid mysterious type errors at the point of marshalling. */ public final void writeMap(@Nullable Map val) { - assertNotRecycled(); writeMapInternal((Map<String, Object>) val); } @@ -1408,7 +1367,6 @@ public final class Parcel { * growing dataCapacity() if needed. The Map keys must be String objects. */ /* package */ void writeMapInternal(@Nullable Map<String,Object> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1434,7 +1392,6 @@ public final class Parcel { * growing dataCapacity() if needed. The Map keys must be String objects. */ /* package */ void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1464,7 +1421,6 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void writeArrayMap(@Nullable ArrayMap<String, Object> val) { - assertNotRecycled(); writeArrayMapInternal(val); } @@ -1483,7 +1439,6 @@ public final class Parcel { */ public <T extends Parcelable> void writeTypedArrayMap(@Nullable ArrayMap<String, T> val, int parcelableFlags) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1505,7 +1460,6 @@ public final class Parcel { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void writeArraySet(@Nullable ArraySet<? extends Object> val) { - assertNotRecycled(); final int size = (val != null) ? val.size() : -1; writeInt(size); for (int i = 0; i < size; i++) { @@ -1518,7 +1472,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeBundle(@Nullable Bundle val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1532,7 +1485,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writePersistableBundle(@Nullable PersistableBundle val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1546,7 +1498,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeSize(@NonNull Size val) { - assertNotRecycled(); writeInt(val.getWidth()); writeInt(val.getHeight()); } @@ -1556,7 +1507,6 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeSizeF(@NonNull SizeF val) { - assertNotRecycled(); writeFloat(val.getWidth()); writeFloat(val.getHeight()); } @@ -1567,7 +1517,6 @@ public final class Parcel { * {@link #writeValue} and must follow the specification there. */ public final void writeList(@Nullable List val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1587,7 +1536,6 @@ public final class Parcel { * {@link #writeValue} and must follow the specification there. */ public final void writeArray(@Nullable Object[] val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1608,7 +1556,6 @@ public final class Parcel { * specification there. */ public final <T> void writeSparseArray(@Nullable SparseArray<T> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1624,7 +1571,6 @@ public final class Parcel { } public final void writeSparseBooleanArray(@Nullable SparseBooleanArray val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1643,7 +1589,6 @@ public final class Parcel { * @hide */ public final void writeSparseIntArray(@Nullable SparseIntArray val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -1659,7 +1604,6 @@ public final class Parcel { } public final void writeBooleanArray(@Nullable boolean[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1694,7 +1638,6 @@ public final class Parcel { } private void ensureWithinMemoryLimit(int typeSize, @NonNull int... dimensions) { - assertNotRecycled(); // For Multidimensional arrays, Calculate total object // which will be allocated. int totalObjects = 1; @@ -1712,7 +1655,6 @@ public final class Parcel { } private void ensureWithinMemoryLimit(int typeSize, int length) { - assertNotRecycled(); int estimatedAllocationSize = 0; try { estimatedAllocationSize = Math.multiplyExact(typeSize, length); @@ -1736,7 +1678,6 @@ public final class Parcel { @Nullable public final boolean[] createBooleanArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_BOOLEAN, N); // >>2 as a fast divide-by-4 works in the create*Array() functions @@ -1754,7 +1695,6 @@ public final class Parcel { } public final void readBooleanArray(@NonNull boolean[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1767,7 +1707,6 @@ public final class Parcel { /** @hide */ public void writeShortArray(@Nullable short[] val) { - assertNotRecycled(); if (val != null) { int n = val.length; writeInt(n); @@ -1782,7 +1721,6 @@ public final class Parcel { /** @hide */ @Nullable public short[] createShortArray() { - assertNotRecycled(); int n = readInt(); ensureWithinMemoryLimit(SIZE_SHORT, n); if (n >= 0 && n <= (dataAvail() >> 2)) { @@ -1798,7 +1736,6 @@ public final class Parcel { /** @hide */ public void readShortArray(@NonNull short[] val) { - assertNotRecycled(); int n = readInt(); if (n == val.length) { for (int i = 0; i < n; i++) { @@ -1810,7 +1747,6 @@ public final class Parcel { } public final void writeCharArray(@Nullable char[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1824,7 +1760,6 @@ public final class Parcel { @Nullable public final char[] createCharArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_CHAR, N); if (N >= 0 && N <= (dataAvail() >> 2)) { @@ -1839,7 +1774,6 @@ public final class Parcel { } public final void readCharArray(@NonNull char[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1851,7 +1785,6 @@ public final class Parcel { } public final void writeIntArray(@Nullable int[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1865,7 +1798,6 @@ public final class Parcel { @Nullable public final int[] createIntArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_INT, N); if (N >= 0 && N <= (dataAvail() >> 2)) { @@ -1880,7 +1812,6 @@ public final class Parcel { } public final void readIntArray(@NonNull int[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1892,7 +1823,6 @@ public final class Parcel { } public final void writeLongArray(@Nullable long[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1906,7 +1836,6 @@ public final class Parcel { @Nullable public final long[] createLongArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_LONG, N); // >>3 because stored longs are 64 bits @@ -1922,7 +1851,6 @@ public final class Parcel { } public final void readLongArray(@NonNull long[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1934,7 +1862,6 @@ public final class Parcel { } public final void writeFloatArray(@Nullable float[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1948,7 +1875,6 @@ public final class Parcel { @Nullable public final float[] createFloatArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_FLOAT, N); // >>2 because stored floats are 4 bytes @@ -1964,7 +1890,6 @@ public final class Parcel { } public final void readFloatArray(@NonNull float[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -1976,7 +1901,6 @@ public final class Parcel { } public final void writeDoubleArray(@Nullable double[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -1990,7 +1914,6 @@ public final class Parcel { @Nullable public final double[] createDoubleArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_DOUBLE, N); // >>3 because stored doubles are 8 bytes @@ -2006,7 +1929,6 @@ public final class Parcel { } public final void readDoubleArray(@NonNull double[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2018,24 +1940,20 @@ public final class Parcel { } public final void writeStringArray(@Nullable String[] val) { - assertNotRecycled(); writeString16Array(val); } @Nullable public final String[] createStringArray() { - assertNotRecycled(); return createString16Array(); } public final void readStringArray(@NonNull String[] val) { - assertNotRecycled(); readString16Array(val); } /** {@hide} */ public final void writeString8Array(@Nullable String[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2050,7 +1968,6 @@ public final class Parcel { /** {@hide} */ @Nullable public final String[] createString8Array() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2066,7 +1983,6 @@ public final class Parcel { /** {@hide} */ public final void readString8Array(@NonNull String[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2079,7 +1995,6 @@ public final class Parcel { /** {@hide} */ public final void writeString16Array(@Nullable String[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2094,7 +2009,6 @@ public final class Parcel { /** {@hide} */ @Nullable public final String[] createString16Array() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2110,7 +2024,6 @@ public final class Parcel { /** {@hide} */ public final void readString16Array(@NonNull String[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2122,7 +2035,6 @@ public final class Parcel { } public final void writeBinderArray(@Nullable IBinder[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2147,7 +2059,6 @@ public final class Parcel { */ public final <T extends IInterface> void writeInterfaceArray( @SuppressLint("ArrayReturn") @Nullable T[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2163,7 +2074,6 @@ public final class Parcel { * @hide */ public final void writeCharSequenceArray(@Nullable CharSequence[] val) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2179,7 +2089,6 @@ public final class Parcel { * @hide */ public final void writeCharSequenceList(@Nullable ArrayList<CharSequence> val) { - assertNotRecycled(); if (val != null) { int N = val.size(); writeInt(N); @@ -2193,7 +2102,6 @@ public final class Parcel { @Nullable public final IBinder[] createBinderArray() { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2208,7 +2116,6 @@ public final class Parcel { } public final void readBinderArray(@NonNull IBinder[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2230,7 +2137,6 @@ public final class Parcel { @Nullable public final <T extends IInterface> T[] createInterfaceArray( @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) { - assertNotRecycled(); int N = readInt(); ensureWithinMemoryLimit(SIZE_COMPLEX_TYPE, N); if (N >= 0) { @@ -2255,7 +2161,6 @@ public final class Parcel { public final <T extends IInterface> void readInterfaceArray( @SuppressLint("ArrayReturn") @NonNull T[] val, @NonNull Function<IBinder, T> asInterface) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -2281,7 +2186,6 @@ public final class Parcel { * @see Parcelable */ public final <T extends Parcelable> void writeTypedList(@Nullable List<T> val) { - assertNotRecycled(); writeTypedList(val, 0); } @@ -2301,7 +2205,6 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedSparseArray(@Nullable SparseArray<T> val, int parcelableFlags) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2331,7 +2234,6 @@ public final class Parcel { * @see Parcelable */ public <T extends Parcelable> void writeTypedList(@Nullable List<T> val, int parcelableFlags) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2357,7 +2259,6 @@ public final class Parcel { * @see #readStringList */ public final void writeStringList(@Nullable List<String> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2383,7 +2284,6 @@ public final class Parcel { * @see #readBinderList */ public final void writeBinderList(@Nullable List<IBinder> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2406,7 +2306,6 @@ public final class Parcel { * @see #readInterfaceList */ public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2428,7 +2327,6 @@ public final class Parcel { * @see #readParcelableList(List, ClassLoader) */ public final <T extends Parcelable> void writeParcelableList(@Nullable List<T> val, int flags) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2463,7 +2361,6 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedArray(@Nullable T[] val, int parcelableFlags) { - assertNotRecycled(); if (val != null) { int N = val.length; writeInt(N); @@ -2486,7 +2383,6 @@ public final class Parcel { */ public final <T extends Parcelable> void writeTypedObject(@Nullable T val, int parcelableFlags) { - assertNotRecycled(); if (val != null) { writeInt(1); val.writeToParcel(this, parcelableFlags); @@ -2524,7 +2420,6 @@ public final class Parcel { */ public <T> void writeFixedArray(@Nullable T val, int parcelableFlags, @NonNull int... dimensions) { - assertNotRecycled(); if (val == null) { writeInt(-1); return; @@ -2636,7 +2531,6 @@ public final class Parcel { * should be used).</p> */ public final void writeValue(@Nullable Object v) { - assertNotRecycled(); if (v instanceof LazyValue) { LazyValue value = (LazyValue) v; value.writeToParcel(this); @@ -2754,7 +2648,6 @@ public final class Parcel { * @hide */ public void writeValue(int type, @Nullable Object v) { - assertNotRecycled(); switch (type) { case VAL_NULL: break; @@ -2868,7 +2761,6 @@ public final class Parcel { * {@link Parcelable#writeToParcel(Parcel, int) Parcelable.writeToParcel()}. */ public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) { - assertNotRecycled(); if (p == null) { writeString(null); return; @@ -2884,7 +2776,6 @@ public final class Parcel { * @see #readParcelableCreator */ public final void writeParcelableCreator(@NonNull Parcelable p) { - assertNotRecycled(); String name = p.getClass().getName(); writeString(name); } @@ -2923,7 +2814,6 @@ public final class Parcel { */ @TestApi public boolean allowSquashing() { - assertNotRecycled(); boolean previous = mAllowSquashing; mAllowSquashing = true; return previous; @@ -2935,7 +2825,6 @@ public final class Parcel { */ @TestApi public void restoreAllowSquashing(boolean previous) { - assertNotRecycled(); mAllowSquashing = previous; if (!mAllowSquashing) { mWrittenSquashableParcelables = null; @@ -2992,7 +2881,6 @@ public final class Parcel { * @hide */ public boolean maybeWriteSquashed(@NonNull Parcelable p) { - assertNotRecycled(); if (!mAllowSquashing) { // Don't squash, and don't put it in the map either. writeInt(0); @@ -3043,7 +2931,6 @@ public final class Parcel { @SuppressWarnings("unchecked") @Nullable public <T extends Parcelable> T readSquashed(SquashReadHelper<T> reader) { - assertNotRecycled(); final int offset = readInt(); final int pos = dataPosition(); @@ -3077,7 +2964,6 @@ public final class Parcel { * using the other approaches to writing data in to a Parcel. */ public final void writeSerializable(@Nullable Serializable s) { - assertNotRecycled(); if (s == null) { writeString(null); return; @@ -3130,7 +3016,6 @@ public final class Parcel { */ @RavenwoodReplace(blockedBy = AppOpsManager.class) public final void writeException(@NonNull Exception e) { - assertNotRecycled(); AppOpsManager.prefixParcelWithAppOpsIfNeeded(this); int code = getExceptionCode(e); @@ -3211,7 +3096,6 @@ public final class Parcel { /** @hide */ public void writeStackTrace(@NonNull Throwable e) { - assertNotRecycled(); final int sizePosition = dataPosition(); writeInt(0); // Header size will be filled in later StackTraceElement[] stackTrace = e.getStackTrace(); @@ -3237,7 +3121,6 @@ public final class Parcel { */ @RavenwoodReplace(blockedBy = AppOpsManager.class) public final void writeNoException() { - assertNotRecycled(); AppOpsManager.prefixParcelWithAppOpsIfNeeded(this); // Despite the name of this function ("write no exception"), @@ -3281,7 +3164,6 @@ public final class Parcel { * @see #writeNoException */ public final void readException() { - assertNotRecycled(); int code = readExceptionCode(); if (code != 0) { String msg = readString(); @@ -3305,7 +3187,6 @@ public final class Parcel { @UnsupportedAppUsage @TestApi public final int readExceptionCode() { - assertNotRecycled(); int code = readInt(); if (code == EX_HAS_NOTED_APPOPS_REPLY_HEADER) { AppOpsManager.readAndLogNotedAppops(this); @@ -3339,7 +3220,6 @@ public final class Parcel { * @param msg The exception message. */ public final void readException(int code, String msg) { - assertNotRecycled(); String remoteStackTrace = null; final int remoteStackPayloadSize = readInt(); if (remoteStackPayloadSize > 0) { @@ -3370,7 +3250,6 @@ public final class Parcel { /** @hide */ public Exception createExceptionOrNull(int code, String msg) { - assertNotRecycled(); switch (code) { case EX_PARCELABLE: if (readInt() > 0) { @@ -3403,7 +3282,6 @@ public final class Parcel { * Read an integer value from the parcel at the current dataPosition(). */ public final int readInt() { - assertNotRecycled(); return nativeReadInt(mNativePtr); } @@ -3411,7 +3289,6 @@ public final class Parcel { * Read a long integer value from the parcel at the current dataPosition(). */ public final long readLong() { - assertNotRecycled(); return nativeReadLong(mNativePtr); } @@ -3420,7 +3297,6 @@ public final class Parcel { * dataPosition(). */ public final float readFloat() { - assertNotRecycled(); return nativeReadFloat(mNativePtr); } @@ -3429,7 +3305,6 @@ public final class Parcel { * current dataPosition(). */ public final double readDouble() { - assertNotRecycled(); return nativeReadDouble(mNativePtr); } @@ -3438,19 +3313,16 @@ public final class Parcel { */ @Nullable public final String readString() { - assertNotRecycled(); return readString16(); } /** {@hide} */ public final @Nullable String readString8() { - assertNotRecycled(); return mReadWriteHelper.readString8(this); } /** {@hide} */ public final @Nullable String readString16() { - assertNotRecycled(); return mReadWriteHelper.readString16(this); } @@ -3462,19 +3334,16 @@ public final class Parcel { * @hide */ public @Nullable String readStringNoHelper() { - assertNotRecycled(); return readString16NoHelper(); } /** {@hide} */ public @Nullable String readString8NoHelper() { - assertNotRecycled(); return nativeReadString8(mNativePtr); } /** {@hide} */ public @Nullable String readString16NoHelper() { - assertNotRecycled(); return nativeReadString16(mNativePtr); } @@ -3482,7 +3351,6 @@ public final class Parcel { * Read a boolean value from the parcel at the current dataPosition(). */ public final boolean readBoolean() { - assertNotRecycled(); return readInt() != 0; } @@ -3493,7 +3361,6 @@ public final class Parcel { @UnsupportedAppUsage @Nullable public final CharSequence readCharSequence() { - assertNotRecycled(); return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(this); } @@ -3501,7 +3368,6 @@ public final class Parcel { * Read an object from the parcel at the current dataPosition(). */ public final IBinder readStrongBinder() { - assertNotRecycled(); final IBinder result = nativeReadStrongBinder(mNativePtr); // If it's a reply from a method with @PropagateAllowBlocking, then inherit allow-blocking @@ -3517,7 +3383,6 @@ public final class Parcel { * Read a FileDescriptor from the parcel at the current dataPosition(). */ public final ParcelFileDescriptor readFileDescriptor() { - assertNotRecycled(); FileDescriptor fd = nativeReadFileDescriptor(mNativePtr); return fd != null ? new ParcelFileDescriptor(fd) : null; } @@ -3525,7 +3390,6 @@ public final class Parcel { /** {@hide} */ @UnsupportedAppUsage public final FileDescriptor readRawFileDescriptor() { - assertNotRecycled(); return nativeReadFileDescriptor(mNativePtr); } @@ -3536,7 +3400,6 @@ public final class Parcel { **/ @Nullable public final FileDescriptor[] createRawFileDescriptorArray() { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -3556,7 +3419,6 @@ public final class Parcel { * @return the FileDescriptor array, or null if the array is null. **/ public final void readRawFileDescriptorArray(FileDescriptor[] val) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -3571,7 +3433,6 @@ public final class Parcel { * Read a byte value from the parcel at the current dataPosition(). */ public final byte readByte() { - assertNotRecycled(); return (byte)(readInt() & 0xff); } @@ -3586,7 +3447,6 @@ public final class Parcel { */ @Deprecated public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { - assertNotRecycled(); readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null); } @@ -3600,7 +3460,6 @@ public final class Parcel { public <K, V> void readMap(@NonNull Map<? super K, ? super V> outVal, @Nullable ClassLoader loader, @NonNull Class<K> clazzKey, @NonNull Class<V> clazzValue) { - assertNotRecycled(); Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); readMapInternal(outVal, loader, clazzKey, clazzValue); @@ -3619,7 +3478,6 @@ public final class Parcel { */ @Deprecated public final void readList(@NonNull List outVal, @Nullable ClassLoader loader) { - assertNotRecycled(); int N = readInt(); readListInternal(outVal, N, loader, /* clazz */ null); } @@ -3641,7 +3499,6 @@ public final class Parcel { */ public <T> void readList(@NonNull List<? super T> outVal, @Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); int n = readInt(); readListInternal(outVal, n, loader, clazz); @@ -3661,7 +3518,6 @@ public final class Parcel { @Deprecated @Nullable public HashMap readHashMap(@Nullable ClassLoader loader) { - assertNotRecycled(); return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null); } @@ -3676,7 +3532,6 @@ public final class Parcel { @Nullable public <K, V> HashMap<K, V> readHashMap(@Nullable ClassLoader loader, @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { - assertNotRecycled(); Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); return readHashMapInternal(loader, clazzKey, clazzValue); @@ -3689,7 +3544,6 @@ public final class Parcel { */ @Nullable public final Bundle readBundle() { - assertNotRecycled(); return readBundle(null); } @@ -3701,7 +3555,6 @@ public final class Parcel { */ @Nullable public final Bundle readBundle(@Nullable ClassLoader loader) { - assertNotRecycled(); int length = readInt(); if (length < 0) { if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); @@ -3722,7 +3575,6 @@ public final class Parcel { */ @Nullable public final PersistableBundle readPersistableBundle() { - assertNotRecycled(); return readPersistableBundle(null); } @@ -3734,7 +3586,6 @@ public final class Parcel { */ @Nullable public final PersistableBundle readPersistableBundle(@Nullable ClassLoader loader) { - assertNotRecycled(); int length = readInt(); if (length < 0) { if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length); @@ -3753,7 +3604,6 @@ public final class Parcel { */ @NonNull public final Size readSize() { - assertNotRecycled(); final int width = readInt(); final int height = readInt(); return new Size(width, height); @@ -3764,7 +3614,6 @@ public final class Parcel { */ @NonNull public final SizeF readSizeF() { - assertNotRecycled(); final float width = readFloat(); final float height = readFloat(); return new SizeF(width, height); @@ -3775,7 +3624,6 @@ public final class Parcel { */ @Nullable public final byte[] createByteArray() { - assertNotRecycled(); return nativeCreateByteArray(mNativePtr); } @@ -3784,7 +3632,6 @@ public final class Parcel { * given byte array. */ public final void readByteArray(@NonNull byte[] val) { - assertNotRecycled(); boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0); if (!valid) { throw new RuntimeException("bad array lengths"); @@ -3797,7 +3644,6 @@ public final class Parcel { */ @Nullable public final byte[] readBlob() { - assertNotRecycled(); return nativeReadBlob(mNativePtr); } @@ -3808,7 +3654,6 @@ public final class Parcel { @UnsupportedAppUsage @Nullable public final String[] readStringArray() { - assertNotRecycled(); return createString16Array(); } @@ -3818,7 +3663,6 @@ public final class Parcel { */ @Nullable public final CharSequence[] readCharSequenceArray() { - assertNotRecycled(); CharSequence[] array = null; int length = readInt(); @@ -3841,7 +3685,6 @@ public final class Parcel { */ @Nullable public final ArrayList<CharSequence> readCharSequenceList() { - assertNotRecycled(); ArrayList<CharSequence> array = null; int length = readInt(); @@ -3871,7 +3714,6 @@ public final class Parcel { @Deprecated @Nullable public ArrayList readArrayList(@Nullable ClassLoader loader) { - assertNotRecycled(); return readArrayListInternal(loader, /* clazz */ null); } @@ -3894,7 +3736,6 @@ public final class Parcel { @Nullable public <T> ArrayList<T> readArrayList(@Nullable ClassLoader loader, @NonNull Class<? extends T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readArrayListInternal(loader, clazz); } @@ -3914,7 +3755,6 @@ public final class Parcel { @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader loader) { - assertNotRecycled(); return readArrayInternal(loader, /* clazz */ null); } @@ -3936,7 +3776,6 @@ public final class Parcel { @SuppressLint({"ArrayReturn", "NullableCollection"}) @Nullable public <T> T[] readArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readArrayInternal(loader, clazz); } @@ -3956,7 +3795,6 @@ public final class Parcel { @Deprecated @Nullable public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader) { - assertNotRecycled(); return readSparseArrayInternal(loader, /* clazz */ null); } @@ -3978,7 +3816,6 @@ public final class Parcel { @Nullable public <T> SparseArray<T> readSparseArray(@Nullable ClassLoader loader, @NonNull Class<? extends T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readSparseArrayInternal(loader, clazz); } @@ -3990,7 +3827,6 @@ public final class Parcel { */ @Nullable public final SparseBooleanArray readSparseBooleanArray() { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4007,7 +3843,6 @@ public final class Parcel { */ @Nullable public final SparseIntArray readSparseIntArray() { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4032,7 +3867,6 @@ public final class Parcel { */ @Nullable public final <T> ArrayList<T> createTypedArrayList(@NonNull Parcelable.Creator<T> c) { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4056,7 +3890,6 @@ public final class Parcel { * @see #writeTypedList */ public final <T> void readTypedList(@NonNull List<T> list, @NonNull Parcelable.Creator<T> c) { - assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4086,7 +3919,6 @@ public final class Parcel { */ public final @Nullable <T extends Parcelable> SparseArray<T> createTypedSparseArray( @NonNull Parcelable.Creator<T> creator) { - assertNotRecycled(); final int count = readInt(); if (count < 0) { return null; @@ -4116,7 +3948,6 @@ public final class Parcel { */ public final @Nullable <T extends Parcelable> ArrayMap<String, T> createTypedArrayMap( @NonNull Parcelable.Creator<T> creator) { - assertNotRecycled(); final int count = readInt(); if (count < 0) { return null; @@ -4144,7 +3975,6 @@ public final class Parcel { */ @Nullable public final ArrayList<String> createStringArrayList() { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4171,7 +4001,6 @@ public final class Parcel { */ @Nullable public final ArrayList<IBinder> createBinderArrayList() { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4199,7 +4028,6 @@ public final class Parcel { @Nullable public final <T extends IInterface> ArrayList<T> createInterfaceArrayList( @NonNull Function<IBinder, T> asInterface) { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4220,7 +4048,6 @@ public final class Parcel { * @see #writeStringList */ public final void readStringList(@NonNull List<String> list) { - assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4242,7 +4069,6 @@ public final class Parcel { * @see #writeBinderList */ public final void readBinderList(@NonNull List<IBinder> list) { - assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4265,7 +4091,6 @@ public final class Parcel { */ public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list, @NonNull Function<IBinder, T> asInterface) { - assertNotRecycled(); int M = list.size(); int N = readInt(); int i = 0; @@ -4297,7 +4122,6 @@ public final class Parcel { @NonNull public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list, @Nullable ClassLoader cl) { - assertNotRecycled(); return readParcelableListInternal(list, cl, /*clazz*/ null); } @@ -4319,7 +4143,6 @@ public final class Parcel { @NonNull public <T> List<T> readParcelableList(@NonNull List<T> list, @Nullable ClassLoader cl, @NonNull Class<? extends T> clazz) { - assertNotRecycled(); Objects.requireNonNull(list); Objects.requireNonNull(clazz); return readParcelableListInternal(list, cl, clazz); @@ -4365,7 +4188,6 @@ public final class Parcel { */ @Nullable public final <T> T[] createTypedArray(@NonNull Parcelable.Creator<T> c) { - assertNotRecycled(); int N = readInt(); if (N < 0) { return null; @@ -4379,7 +4201,6 @@ public final class Parcel { } public final <T> void readTypedArray(@NonNull T[] val, @NonNull Parcelable.Creator<T> c) { - assertNotRecycled(); int N = readInt(); if (N == val.length) { for (int i=0; i<N; i++) { @@ -4396,7 +4217,6 @@ public final class Parcel { */ @Deprecated public final <T> T[] readTypedArray(Parcelable.Creator<T> c) { - assertNotRecycled(); return createTypedArray(c); } @@ -4413,7 +4233,6 @@ public final class Parcel { */ @Nullable public final <T> T readTypedObject(@NonNull Parcelable.Creator<T> c) { - assertNotRecycled(); if (readInt() != 0) { return c.createFromParcel(this); } else { @@ -4440,7 +4259,6 @@ public final class Parcel { * @see #readTypedArray */ public <T> void readFixedArray(@NonNull T val) { - assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (componentType == boolean.class) { readBooleanArray((boolean[]) val); @@ -4481,7 +4299,6 @@ public final class Parcel { */ public <T, S extends IInterface> void readFixedArray(@NonNull T val, @NonNull Function<IBinder, S> asInterface) { - assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (IInterface.class.isAssignableFrom(componentType)) { readInterfaceArray((S[]) val, asInterface); @@ -4508,7 +4325,6 @@ public final class Parcel { */ public <T, S extends Parcelable> void readFixedArray(@NonNull T val, @NonNull Parcelable.Creator<S> c) { - assertNotRecycled(); Class<?> componentType = val.getClass().getComponentType(); if (Parcelable.class.isAssignableFrom(componentType)) { readTypedArray((S[]) val, c); @@ -4566,7 +4382,6 @@ public final class Parcel { */ @Nullable public <T> T createFixedArray(@NonNull Class<T> cls, @NonNull int... dimensions) { - assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4640,7 +4455,6 @@ public final class Parcel { @Nullable public <T, S extends IInterface> T createFixedArray(@NonNull Class<T> cls, @NonNull Function<IBinder, S> asInterface, @NonNull int... dimensions) { - assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4701,7 +4515,6 @@ public final class Parcel { @Nullable public <T, S extends Parcelable> T createFixedArray(@NonNull Class<T> cls, @NonNull Parcelable.Creator<S> c, @NonNull int... dimensions) { - assertNotRecycled(); // Check if type matches with dimensions // If type is one-dimensional array, delegate to other creators // Otherwise, create an multi-dimensional array at once and then fill it with readFixedArray @@ -4765,7 +4578,6 @@ public final class Parcel { */ public final <T extends Parcelable> void writeParcelableArray(@Nullable T[] value, int parcelableFlags) { - assertNotRecycled(); if (value != null) { int N = value.length; writeInt(N); @@ -4784,7 +4596,6 @@ public final class Parcel { */ @Nullable public final Object readValue(@Nullable ClassLoader loader) { - assertNotRecycled(); return readValue(loader, /* clazz */ null); } @@ -4840,7 +4651,6 @@ public final class Parcel { */ @Nullable public Object readLazyValue(@Nullable ClassLoader loader) { - assertNotRecycled(); int start = dataPosition(); int type = readInt(); if (isLengthPrefixed(type)) { @@ -5243,7 +5053,6 @@ public final class Parcel { @Deprecated @Nullable public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) { - assertNotRecycled(); return readParcelableInternal(loader, /* clazz */ null); } @@ -5263,7 +5072,6 @@ public final class Parcel { */ @Nullable public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readParcelableInternal(loader, clazz); } @@ -5292,7 +5100,6 @@ public final class Parcel { @Nullable public final <T extends Parcelable> T readCreator(@NonNull Parcelable.Creator<?> creator, @Nullable ClassLoader loader) { - assertNotRecycled(); if (creator instanceof Parcelable.ClassLoaderCreator<?>) { Parcelable.ClassLoaderCreator<?> classLoaderCreator = (Parcelable.ClassLoaderCreator<?>) creator; @@ -5320,7 +5127,6 @@ public final class Parcel { @Deprecated @Nullable public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) { - assertNotRecycled(); return readParcelableCreatorInternal(loader, /* clazz */ null); } @@ -5341,7 +5147,6 @@ public final class Parcel { @Nullable public <T> Parcelable.Creator<T> readParcelableCreator( @Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readParcelableCreatorInternal(loader, clazz); } @@ -5464,7 +5269,6 @@ public final class Parcel { @Deprecated @Nullable public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) { - assertNotRecycled(); return readParcelableArrayInternal(loader, /* clazz */ null); } @@ -5485,7 +5289,6 @@ public final class Parcel { @SuppressLint({"ArrayReturn", "NullableCollection"}) @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); return readParcelableArrayInternal(loader, requireNonNull(clazz)); } @@ -5519,7 +5322,6 @@ public final class Parcel { @Deprecated @Nullable public Serializable readSerializable() { - assertNotRecycled(); return readSerializableInternal(/* loader */ null, /* clazz */ null); } @@ -5536,7 +5338,6 @@ public final class Parcel { */ @Nullable public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { - assertNotRecycled(); Objects.requireNonNull(clazz); return readSerializableInternal( loader == null ? getClass().getClassLoader() : loader, clazz); @@ -5778,7 +5579,6 @@ public final class Parcel { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void readArrayMap(@NonNull ArrayMap<? super String, Object> outVal, @Nullable ClassLoader loader) { - assertNotRecycled(); final int N = readInt(); if (N < 0) { return; @@ -5795,7 +5595,6 @@ public final class Parcel { */ @UnsupportedAppUsage public @Nullable ArraySet<? extends Object> readArraySet(@Nullable ClassLoader loader) { - assertNotRecycled(); final int size = readInt(); if (size < 0) { return null; @@ -5935,7 +5734,6 @@ public final class Parcel { * @hide For testing */ public long getOpenAshmemSize() { - assertNotRecycled(); return nativeGetOpenAshmemSize(mNativePtr); } diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java index 4b16c1dce463..6431f3ce73f3 100644 --- a/core/java/android/os/TestLooperManager.java +++ b/core/java/android/os/TestLooperManager.java @@ -14,6 +14,8 @@ package android.os; +import android.annotation.FlaggedApi; +import android.annotation.Nullable; import android.util.ArraySet; import java.util.concurrent.LinkedBlockingQueue; @@ -93,9 +95,48 @@ public class TestLooperManager { } /** - * Releases the looper to continue standard looping and processing of messages, - * no further interactions with TestLooperManager will be allowed after - * release() has been called. + * Returns the next message that should be executed by this queue, and removes it from the + * queue. If the queue is empty or no messages are deliverable, returns null. + * This method never blocks. + * + * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions + * with it have completed. + */ + @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) + @Nullable + public Message pop() { + checkReleased(); + return mQueue.popForTest(); + } + + /** + * Returns the values of {@link Message#when} of the next message that should be executed by + * this queue. If the queue is empty or no messages are deliverable, returns null. + * This method never blocks. + */ + @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) + @SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value + @Nullable + public Long peekWhen() { + checkReleased(); + return mQueue.peekWhenForTest(); + } + + /** + * Checks whether the Looper is currently blocked on a sync barrier. + * + * A Looper is blocked on a sync barrier if there is a Message in the Looper's + * queue that is ready for execution but is behind a sync barrier + */ + @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) + public boolean isBlockedOnSyncBarrier() { + checkReleased(); + return mQueue.isBlockedOnSyncBarrier(); + } + + /** + * Releases the looper to continue standard looping and processing of messages, no further + * interactions with TestLooperManager will be allowed after release() has been called. */ public void release() { synchronized (sHeldLoopers) { diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl index c45c51d15cc9..af56bfe50381 100644 --- a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl +++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl @@ -16,7 +16,7 @@ package android.os.instrumentation; -import android.os.instrumentation.ExecutableMethodFileOffsets; +import android.os.instrumentation.IOffsetCallback; import android.os.instrumentation.MethodDescriptor; import android.os.instrumentation.TargetProcess; @@ -28,6 +28,7 @@ import android.os.instrumentation.TargetProcess; interface IDynamicInstrumentationManager { /** Provides ART metadata about the described compiled method within the target process */ @PermissionManuallyEnforced - @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets( - in TargetProcess targetProcess, in MethodDescriptor methodDescriptor); + void getExecutableMethodFileOffsets( + in TargetProcess targetProcess, in MethodDescriptor methodDescriptor, + in IOffsetCallback callback); } diff --git a/core/java/android/os/instrumentation/IOffsetCallback.aidl b/core/java/android/os/instrumentation/IOffsetCallback.aidl new file mode 100644 index 000000000000..a28c93f5353a --- /dev/null +++ b/core/java/android/os/instrumentation/IOffsetCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.instrumentation; + +import android.os.instrumentation.ExecutableMethodFileOffsets; + +/** + * System private API for providing dynamic instrumentation offset results. + * + * {@hide} + */ +oneway interface IOffsetCallback { + void onResult(in @nullable ExecutableMethodFileOffsets offsets); +} diff --git a/core/java/android/os/instrumentation/MethodDescriptorParser.java b/core/java/android/os/instrumentation/MethodDescriptorParser.java new file mode 100644 index 000000000000..57fc44ff623e --- /dev/null +++ b/core/java/android/os/instrumentation/MethodDescriptorParser.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.instrumentation; + +import android.annotation.NonNull; + +import java.lang.reflect.Method; + +/** + * A utility class for dynamic instrumentation / uprobestats. + * + * @hide + */ +public final class MethodDescriptorParser { + + /** + * Parses a {@link MethodDescriptor} (in string representation) into a {@link Method}. + */ + public static Method parseMethodDescriptor(ClassLoader classLoader, + @NonNull MethodDescriptor descriptor) { + try { + Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName); + Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length]; + for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) { + String typeName = descriptor.fullyQualifiedParameters[i]; + boolean isArrayType = typeName.endsWith("[]"); + if (isArrayType) { + typeName = typeName.substring(0, typeName.length() - 2); + } + switch (typeName) { + case "boolean": + parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class; + break; + case "byte": + parameters[i] = isArrayType ? byte.class.arrayType() : byte.class; + break; + case "char": + parameters[i] = isArrayType ? char.class.arrayType() : char.class; + break; + case "short": + parameters[i] = isArrayType ? short.class.arrayType() : short.class; + break; + case "int": + parameters[i] = isArrayType ? int.class.arrayType() : int.class; + break; + case "long": + parameters[i] = isArrayType ? long.class.arrayType() : long.class; + break; + case "float": + parameters[i] = isArrayType ? float.class.arrayType() : float.class; + break; + case "double": + parameters[i] = isArrayType ? double.class.arrayType() : double.class; + break; + default: + parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType() + : classLoader.loadClass(typeName); + } + } + + return javaClass.getDeclaredMethod(descriptor.methodName, parameters); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new IllegalArgumentException( + "The specified method cannot be found. Is this descriptor valid? " + + descriptor, e); + } + } +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 9935be2c8ef6..4acb6312f90d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10042,6 +10042,12 @@ public final class Settings { "minimal_post_processing_allowed"; /** + * Whether to mirror the built-in display on all connected displays. + * @hide + */ + public static final String MIRROR_BUILT_IN_DISPLAY = "mirror_built_in_display"; + + /** * No mode switching will happen. * * @see #MATCH_CONTENT_FRAME_RATE diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index fba8e42cc673..6d62a2c7db2e 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -341,7 +341,7 @@ public final class FillEventHistory implements Parcelable { /** Credential Manager suggestions are shown instead of Autofill suggestion */ @FlaggedApi(FLAG_AUTOFILL_W_METRICS) - public static final int UI_TYPE_CREDMAN = 4; + public static final int UI_TYPE_CREDENTIAL_MANAGER = 4; /** @hide */ @IntDef(prefix = { "UI_TYPE_" }, value = { diff --git a/core/java/android/service/ondeviceintelligence/OWNERS b/core/java/android/service/ondeviceintelligence/OWNERS deleted file mode 100644 index 09774f78d712..000000000000 --- a/core/java/android/service/ondeviceintelligence/OWNERS +++ /dev/null @@ -1 +0,0 @@ -file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/core/java/android/speech/tts/EventLogTags.logtags b/core/java/android/speech/tts/EventLogTags.logtags index e209a286966a..5ba2baec6fcf 100644 --- a/core/java/android/speech/tts/EventLogTags.logtags +++ b/core/java/android/speech/tts/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.speech.tts; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index c9d560c3424b..802bddd76e30 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -1301,17 +1301,23 @@ public final class Display { } /** - * Represents the {@link FrameRateCategory} for the Normal frame rate + * Normal category determines the framework's recommended normal frame rate. + * Opt for this normal rate unless a higher frame rate significantly enhances + * the user experience. * - * @see FrameRateCategory + * @see #getSuggestedFrameRate(int) + * @see #FRAME_RATE_CATEGORY_HIGH */ @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE) public static final int FRAME_RATE_CATEGORY_NORMAL = 0; /** - * Represents the {@link FrameRateCategory} for the High frame rate + * High category determines the framework's recommended high frame rate. + * Opt for this high rate when a higher frame rate significantly enhances + * the user experience. * - * @see FrameRateCategory + * @see #getSuggestedFrameRate(int) + * @see #FRAME_RATE_CATEGORY_NORMAL */ @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE) public static final int FRAME_RATE_CATEGORY_HIGH = 1; @@ -1332,22 +1338,17 @@ public final class Display { * * <p> For example, an animation that does not require fast render rates can use * the {@link #FRAME_RATE_CATEGORY_NORMAL} to get the suggested frame rate. - * The suggested frame rate then can be used in the - * {@link Surface.FrameRateParams.Builder#setDesiredRateRange} for desiredMinRate. * * <pre>{@code * float desiredMinRate = display.getSuggestedFrameRate(FRAME_RATE_CATEGORY_NORMAL); - * Surface.FrameRateParams params = new Surface.FrameRateParams.Builder(). - * setDesiredRateRange(desiredMinRate, Float.MAX).build(); - * surface.setFrameRate(params); + * surface.setFrameRate(desiredMinRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); * }</pre> * </p> * * @param category either {@link #FRAME_RATE_CATEGORY_NORMAL} * or {@link #FRAME_RATE_CATEGORY_HIGH} * - * @see Surface#setFrameRate(Surface.FrameRateParams) - * @see SurfaceControl.Transaction#setFrameRate(SurfaceControl, Surface.FrameRateParams) + * @see Surface#setFrameRate(float, int) * @throws IllegalArgumentException when category is not {@link #FRAME_RATE_CATEGORY_NORMAL} * or {@link #FRAME_RATE_CATEGORY_HIGH} */ diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 5da49857dda5..072a037aa84a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -933,6 +933,27 @@ interface IWindowManager void detachWindowContext(IBinder clientToken); /** + * Reparents the {@link android.window.WindowContext} to the + * {@link com.android.server.wm.DisplayArea} on another display. + * This method also reparent the WindowContext associated WindowToken to another display if + * necessary. + * <p> + * {@code type} and {@code options} must be the same as the previous call of + * {@link #attachWindowContextToDisplayArea} on the same Context otherwise this will fail + * silently. + * + * @param appThread the process that the window context is on. + * @param clientToken the window context's token + * @param type The window type of the WindowContext + * @param displayId The new display id this context windows should be parented to + * @param options Bundle the context was created with + * + * @return True if the operation was successful, False otherwise. + */ + boolean reparentWindowContextToDisplayArea(in IApplicationThread appThread, + IBinder clientToken, int displayId); + + /** * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled. * * @param listener the listener to be registered diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 67050e01b591..213ece09da22 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -296,8 +296,8 @@ public class InsetsState implements Parcelable { @SoftInputModeFlags int softInputMode, int windowFlags) { final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST; final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING - ? systemBars() | ime() - : systemBars(); + ? systemBars() | displayCutout() | ime() + : systemBars() | displayCutout(); @InsetsType int forceConsumingTypes = 0; Insets insets = Insets.NONE; for (int i = mSources.size() - 1; i >= 0; i--) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0681745a0fc6..d15b0f518f83 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17184,7 +17184,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final WindowManager windowManager = mContext.getSystemService(WindowManager.class); final WindowMetrics metrics = windowManager.getMaximumWindowMetrics(); final Insets insets = metrics.getWindowInsets().getInsets( - WindowInsets.Type.navigationBars() | WindowInsets.Type.displayCutout()); + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); outRect.set(metrics.getBounds()); outRect.inset(insets); outRect.offsetTo(0, 0); diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index 63bf392b5ef1..9e97a8eb58aa 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -591,7 +591,7 @@ public class ViewConfiguration { res.getBoolean( com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled); mViewTouchScreenHapticScrollFeedbackEnabled = - Flags.enableTouchScrollFeedback() + Flags.enableScrollFeedbackForTouch() ? res.getBoolean( com.android.internal.R.bool .config_viewTouchScreenHapticScrollFeedbackEnabled) diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 1be7f4849f07..43a946a234ba 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -87,8 +87,12 @@ public abstract class ViewStructure { * <p>This value is added to mainly help with debugging purpose. */ @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + @SuppressWarnings( + "ActionValue") // Lint expects this as + // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER + // but should not have contentcapture public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = - "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE"; + "android.view.extra.VIRTUAL_STRUCTURE_TYPE"; /** * Key used for specifying the version of the view that generated the virtual structure for @@ -98,8 +102,12 @@ public abstract class ViewStructure { * "104.0.5112.69", then the value should be "104.0.5112.69" */ @FlaggedApi(FLAG_AUTOFILL_W_METRICS) + @SuppressWarnings( + "ActionValue") // Lint expects this as + // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE + // but should not have contentcapture public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = - "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; + "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER"; /** * Set the identifier for this view. diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 1de0474182dd..60e528c5fb58 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -26,6 +26,7 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE; import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS; +import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.service.autofill.Flags.relayoutFix; import static android.view.ContentInfo.SOURCE_AUTOFILL; import static android.view.autofill.Helper.sDebug; @@ -787,6 +788,11 @@ public final class AutofillManager { private AutofillStateFingerprint mAutofillStateFingerprint; + /** + * Whether improveFillDialog feature is enabled or not. + */ + private boolean mImproveFillDialogEnabled; + /** @hide */ public interface AutofillClient { /** @@ -1017,6 +1023,17 @@ public final class AutofillManager { mRelayoutFix = relayoutFix() && AutofillFeatureFlags.enableRelayoutFixes(); mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout(); mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration(); + mImproveFillDialogEnabled = + improveFillDialogAconfig() && AutofillFeatureFlags.isImproveFillDialogEnabled(); + } + + /** + * Whether improvement to fill dialog is enabled. + * + * @hide + */ + public boolean isImproveFillDialogEnabled() { + return mImproveFillDialogEnabled; } /** @@ -1679,6 +1696,11 @@ public final class AutofillManager { private void notifyViewReadyInner(AutofillId id, @Nullable String[] autofillHints, boolean isCredmanRequested) { + if (isImproveFillDialogEnabled() && !isCredmanRequested) { + // We do not want to send pre-trigger request. + // TODO(b/377868687): verify if we can remove the flow for isCredmanRequested too. + return; + } if (sDebug) { Log.d(TAG, "notifyViewReadyInner:" + id); } @@ -2046,6 +2068,34 @@ public final class AutofillManager { } /** + * Notify autofill system that IME animation has started + * @param startTimeMs start time as measured by SystemClock.elapsedRealtime() + */ + void notifyImeAnimationStart(long startTimeMs) { + try { + mService.notifyImeAnimationStart(mSessionId, startTimeMs, mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the + // server side. Just log the exception and move-on. + Log.w(TAG, "notifyImeAnimationStart(): RemoteException caught but ignored " + e); + } + } + + /** + * Notify autofill system that IME animation has ended + * @param endTimeMs end time as measured by SystemClock.elapsedRealtime() + */ + void notifyImeAnimationEnd(long endTimeMs) { + try { + mService.notifyImeAnimationEnd(mSessionId, endTimeMs, mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the + // server side. Just log the exception and move-on. + Log.w(TAG, "notifyImeAnimationStart(): RemoteException caught but ignored " + e); + } + } + + /** * Called when a virtual view that supports autofill is exited. * * @param view the virtual view parent. @@ -4050,6 +4100,10 @@ public final class AutofillManager { @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS) @Deprecated public boolean showAutofillDialog(@NonNull View view) { + if (isImproveFillDialogEnabled()) { + Log.i(TAG, "showAutofillDialog() return false due to improve fill dialog"); + return false; + } Objects.requireNonNull(view); if (shouldShowAutofillDialog(view, view.getAutofillId())) { mShowAutofillDialogCalled = true; @@ -4093,6 +4147,10 @@ public final class AutofillManager { @FlaggedApi(FLAG_FILL_DIALOG_IMPROVEMENTS) @Deprecated public boolean showAutofillDialog(@NonNull View view, int virtualId) { + if (isImproveFillDialogEnabled()) { + Log.i(TAG, "showAutofillDialog() return false due to improve fill dialog"); + return false; + } Objects.requireNonNull(view); if (shouldShowAutofillDialog(view, getAutofillId(view, virtualId))) { mShowAutofillDialogCalled = true; @@ -4117,7 +4175,7 @@ public final class AutofillManager { return false; } - if (getImeStateFlag(view) == FLAG_IME_SHOWING) { + if (getImeStateFlag(view) == FLAG_IME_SHOWING && !isImproveFillDialogEnabled()) { // IME is showing return false; } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index f67405f7c1e4..28f8577beed7 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -70,4 +70,6 @@ oneway interface IAutoFillManager { void notifyNotExpiringResponseDuringAuth(int sessionId, int userId); void notifyViewEnteredIgnoredDuringAuthCount(int sessionId, int userId); void setAutofillIdsAttemptedForRefill(int sessionId, in List<AutofillId> ids, int userId); + void notifyImeAnimationStart(int sessionId, long startTimeMs, int userId); + void notifyImeAnimationEnd(int sessionId, long endTimeMs, int userId); } diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig index ebda4d472b0d..ddf6ff11a83a 100644 --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig @@ -17,10 +17,10 @@ flag { } flag { - namespace: "toolkit" - name: "enable_touch_scroll_feedback" + namespace: "wear_frameworks" + name: "enable_scroll_feedback_for_touch" description: "Enables touchscreen haptic scroll feedback" - bug: "331830899" + bug: "382135785" is_fixed_read_only: true } diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags index a90aebd71716..8bbd5a9d0246 100644 --- a/core/java/android/webkit/EventLogTags.logtags +++ b/core/java/android/webkit/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package android.webkit; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3c854ea4fad4..fc3014a0eaec 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -16,7 +16,7 @@ package android.widget; -import static android.view.flags.Flags.enableTouchScrollFeedback; +import static android.view.flags.Flags.enableScrollFeedbackForTouch; import static android.view.flags.Flags.scrollFeedbackApi; import static android.view.flags.Flags.viewVelocityApi; @@ -3737,7 +3737,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te atEdge = trackMotionScroll(deltaY, incrementalDeltaY); // TODO: b/360198915 - Add unit testing for using ScrollFeedbackProvider - if (enableTouchScrollFeedback()) { + if (vtev != null && enableScrollFeedbackForTouch()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollProgress( vtev.getDeviceId(), vtev.getSource(), MotionEvent.AXIS_Y, @@ -3779,7 +3779,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchMode = TOUCH_MODE_OVERSCROLL; } - if (enableTouchScrollFeedback()) { + if (vtev != null && enableScrollFeedbackForTouch()) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollLimit( vtev.getDeviceId(), vtev.getSource(), diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 511c832a4876..184933fb8288 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -16,7 +16,7 @@ package android.widget; -import static android.view.flags.Flags.enableTouchScrollFeedback; +import static android.view.flags.Flags.enableScrollFeedbackForTouch; import static android.view.flags.Flags.viewVelocityApi; import android.annotation.ColorInt; @@ -909,7 +909,7 @@ public class ScrollView extends FrameLayout { } // TODO: b/360198915 - Add unit tests. - if (enableTouchScrollFeedback()) { + if (enableScrollFeedbackForTouch()) { if (hitTopLimit || hitBottomLimit) { initHapticScrollFeedbackProviderIfNotExists(); mHapticScrollFeedbackProvider.onScrollLimit(vtev.getDeviceId(), diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d7750bd412a3..71a832d84f08 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -18,6 +18,7 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.graphics.Paint.NEW_FONT_VARIATION_MANAGEMENT; import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT; import static android.view.ContentInfo.SOURCE_AUTOFILL; import static android.view.ContentInfo.SOURCE_CLIPBOARD; @@ -5542,7 +5543,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && fontVariationSettings.equals(existingSettings))) { return true; } - boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); + + final boolean useFontVariationStore = Flags.typefaceRedesignReadonly() + && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT); + boolean effective; + if (useFontVariationStore) { + if (mFontWeightAdjustment != 0 + && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { + mTextPaint.setFontVariationSettings(fontVariationSettings, mFontWeightAdjustment); + } else { + mTextPaint.setFontVariationSettings(fontVariationSettings); + } + effective = true; + } else { + effective = mTextPaint.setFontVariationSettings(fontVariationSettings); + } if (effective && mLayout != null) { nullLayouts(); @@ -10114,6 +10129,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.extras.putBoolean( STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled); } + if (android.view.inputmethod.Flags.writingTools()) { + // default to same behavior as isSuggestionsEnabled(). + outAttrs.setWritingToolsEnabled(isSuggestionsEnabled()); + } ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); gestures.add(SelectGesture.class); gestures.add(SelectRangeGesture.class); diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index fe936f77de07..f42c0ec5ee7c 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -44,10 +44,10 @@ public final class TransitionRequestInfo implements Parcelable { private @Nullable ActivityManager.RunningTaskInfo mTriggerTask; /** - * If non-null, the task containing the pip activity that participates in this - * transition. + * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both + * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity. */ - private @Nullable ActivityManager.RunningTaskInfo mPipTask; + private @Nullable TransitionRequestInfo.PipChange mPipChange; /** If non-null, a remote-transition associated with the source of this transition. */ private @Nullable RemoteTransition mRemoteTransition; @@ -70,7 +70,7 @@ public final class TransitionRequestInfo implements Parcelable { @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition) { - this(type, triggerTask, null /* pipTask */, + this(type, triggerTask, null /* pipChange */, remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */); } @@ -80,7 +80,7 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable ActivityManager.RunningTaskInfo triggerTask, @Nullable RemoteTransition remoteTransition, int flags) { - this(type, triggerTask, null /* pipTask */, + this(type, triggerTask, null /* pipChange */, remoteTransition, null /* displayChange */, flags, -1 /* debugId */); } @@ -91,7 +91,7 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags) { - this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags, + this(type, triggerTask, null /* pipChange */, remoteTransition, displayChange, flags, -1 /* debugId */); } @@ -103,7 +103,9 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags) { - this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */); + this(type, triggerTask, + pipTask != null ? new TransitionRequestInfo.PipChange(pipTask) : null, + remoteTransition, displayChange, flags, -1 /* debugId */); } /** @hide */ @@ -252,7 +254,7 @@ public final class TransitionRequestInfo implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - protected DisplayChange(@android.annotation.NonNull android.os.Parcel in) { + /* package-private */ DisplayChange(@android.annotation.NonNull android.os.Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -289,7 +291,7 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1697564781403L, + time = 1733334462577L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -302,6 +304,143 @@ public final class TransitionRequestInfo implements Parcelable { } + @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false) + public static final class PipChange implements Parcelable { + // In AE case, we might care about the TF token instead of the task token. + @android.annotation.NonNull + private WindowContainerToken mTaskFragmentToken; + + @android.annotation.NonNull + private ActivityManager.RunningTaskInfo mTaskInfo; + + /** Create empty display-change. */ + public PipChange(ActivityManager.RunningTaskInfo taskInfo) { + mTaskFragmentToken = taskInfo.token; + mTaskInfo = taskInfo; + } + + /** Create a display-change representing a rotation. */ + public PipChange(WindowContainerToken taskFragmentToken, + ActivityManager.RunningTaskInfo taskInfo) { + mTaskFragmentToken = taskFragmentToken; + mTaskInfo = taskInfo; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/TransitionRequestInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public @android.annotation.NonNull WindowContainerToken getTaskFragmentToken() { + return mTaskFragmentToken; + } + + @DataClass.Generated.Member + public @android.annotation.NonNull ActivityManager.RunningTaskInfo getTaskInfo() { + return mTaskInfo; + } + + @DataClass.Generated.Member + public @android.annotation.NonNull PipChange setTaskFragmentToken(@android.annotation.NonNull WindowContainerToken value) { + mTaskFragmentToken = value; + com.android.internal.util.AnnotationValidations.validate( + android.annotation.NonNull.class, null, mTaskFragmentToken); + return this; + } + + @DataClass.Generated.Member + public @android.annotation.NonNull PipChange setTaskInfo(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) { + mTaskInfo = value; + com.android.internal.util.AnnotationValidations.validate( + android.annotation.NonNull.class, null, mTaskInfo); + return this; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "PipChange { " + + "taskFragmentToken = " + mTaskFragmentToken + ", " + + "taskInfo = " + mTaskInfo + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeTypedObject(mTaskFragmentToken, flags); + dest.writeTypedObject(mTaskInfo, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ PipChange(@android.annotation.NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + WindowContainerToken taskFragmentToken = (WindowContainerToken) in.readTypedObject(WindowContainerToken.CREATOR); + ActivityManager.RunningTaskInfo taskInfo = (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + + this.mTaskFragmentToken = taskFragmentToken; + com.android.internal.util.AnnotationValidations.validate( + android.annotation.NonNull.class, null, mTaskFragmentToken); + this.mTaskInfo = taskInfo; + com.android.internal.util.AnnotationValidations.validate( + android.annotation.NonNull.class, null, mTaskInfo); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @android.annotation.NonNull Parcelable.Creator<PipChange> CREATOR + = new Parcelable.Creator<PipChange>() { + @Override + public PipChange[] newArray(int size) { + return new PipChange[size]; + } + + @Override + public PipChange createFromParcel(@android.annotation.NonNull android.os.Parcel in) { + return new PipChange(in); + } + }; + + @DataClass.Generated( + time = 1733334462588L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", + inputSignatures = "private @android.annotation.NonNull android.window.WindowContainerToken mTaskFragmentToken\nprivate @android.annotation.NonNull android.app.ActivityManager.RunningTaskInfo mTaskInfo\nclass PipChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + @@ -326,9 +465,9 @@ public final class TransitionRequestInfo implements Parcelable { * @param triggerTask * If non-null, the task containing the activity whose lifecycle change (start or * finish) has caused this transition to occur. - * @param pipTask - * If non-null, the task containing the pip activity that participates in this - * transition. + * @param pipChange + * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both + * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity. * @param remoteTransition * If non-null, a remote-transition associated with the source of this transition. * @param displayChange @@ -344,7 +483,7 @@ public final class TransitionRequestInfo implements Parcelable { public TransitionRequestInfo( @WindowManager.TransitionType int type, @Nullable ActivityManager.RunningTaskInfo triggerTask, - @Nullable ActivityManager.RunningTaskInfo pipTask, + @Nullable TransitionRequestInfo.PipChange pipChange, @Nullable RemoteTransition remoteTransition, @Nullable TransitionRequestInfo.DisplayChange displayChange, int flags, @@ -353,7 +492,7 @@ public final class TransitionRequestInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); this.mTriggerTask = triggerTask; - this.mPipTask = pipTask; + this.mPipChange = pipChange; this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; @@ -380,12 +519,12 @@ public final class TransitionRequestInfo implements Parcelable { } /** - * If non-null, the task containing the pip activity that participates in this - * transition. + * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both + * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity. */ @DataClass.Generated.Member - public @Nullable ActivityManager.RunningTaskInfo getPipTask() { - return mPipTask; + public @Nullable TransitionRequestInfo.PipChange getPipChange() { + return mPipChange; } /** @@ -433,12 +572,12 @@ public final class TransitionRequestInfo implements Parcelable { } /** - * If non-null, the task containing the pip activity that participates in this - * transition. + * If non-null, this request might lead to a PiP transition; {@code PipChange} caches both + * {@code TaskFragment} token and the {@code TaskInfo} of the task with PiP candidate activity. */ @DataClass.Generated.Member - public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) { - mPipTask = value; + public @android.annotation.NonNull TransitionRequestInfo setPipChange(@android.annotation.NonNull TransitionRequestInfo.PipChange value) { + mPipChange = value; return this; } @@ -471,10 +610,10 @@ public final class TransitionRequestInfo implements Parcelable { return "TransitionRequestInfo { " + "type = " + typeToString() + ", " + "triggerTask = " + mTriggerTask + ", " + - "pipTask = " + mPipTask + ", " + + "pipChange = " + mPipChange + ", " + "remoteTransition = " + mRemoteTransition + ", " + "displayChange = " + mDisplayChange + ", " + - "flags = " + Integer.toHexString(mFlags) + ", " + + "flags = " + mFlags + ", " + "debugId = " + mDebugId + " }"; } @@ -487,13 +626,13 @@ public final class TransitionRequestInfo implements Parcelable { byte flg = 0; if (mTriggerTask != null) flg |= 0x2; - if (mPipTask != null) flg |= 0x4; + if (mPipChange != null) flg |= 0x4; if (mRemoteTransition != null) flg |= 0x8; if (mDisplayChange != null) flg |= 0x10; dest.writeByte(flg); dest.writeInt(mType); if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags); - if (mPipTask != null) dest.writeTypedObject(mPipTask, flags); + if (mPipChange != null) dest.writeTypedObject(mPipChange, flags); if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags); if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags); dest.writeInt(mFlags); @@ -514,7 +653,7 @@ public final class TransitionRequestInfo implements Parcelable { byte flg = in.readByte(); int type = in.readInt(); ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); - ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR); + TransitionRequestInfo.PipChange pipChange = (flg & 0x4) == 0 ? null : (TransitionRequestInfo.PipChange) in.readTypedObject(TransitionRequestInfo.PipChange.CREATOR); RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR); int flags = in.readInt(); @@ -524,7 +663,7 @@ public final class TransitionRequestInfo implements Parcelable { com.android.internal.util.AnnotationValidations.validate( WindowManager.TransitionType.class, null, mType); this.mTriggerTask = triggerTask; - this.mPipTask = pipTask; + this.mPipChange = pipChange; this.mRemoteTransition = remoteTransition; this.mDisplayChange = displayChange; this.mFlags = flags; @@ -548,10 +687,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1697564781438L, + time = 1733334462604L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") + inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.PipChange mPipChange\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nprivate final int mDebugId\n java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java index 2b370b9797e5..84a8b8f5b5b0 100644 --- a/core/java/android/window/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -32,6 +32,7 @@ import android.view.Display; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import java.lang.ref.Reference; @@ -95,20 +96,20 @@ public class WindowContext extends ContextWrapper implements WindowProvider { } /** - * Updates this context to a new displayId. + * Moves this context to another display. * <p> - * Note that this doesn't re-parent previously attached windows (they should be removed and - * re-added manually after this is called). Resources associated with this context will have - * the correct value and configuration for the new display after this is called. + * Note that this re-parents all the previously attached windows. Resources associated with this + * context will have the correct value and configuration for the new display after this is + * called. */ - @Override - public void updateDisplay(int displayId) { - if (displayId == getDisplayId()) { - return; + public void reparentToDisplay(int displayId) { + if (Flags.reparentWindowTokenApi()) { + if (displayId == getDisplayId()) { + return; + } + super.updateDisplay(displayId); + mController.reparentToDisplayArea(mType, displayId, mOptions); } - super.updateDisplay(displayId); - mController.detachIfNeeded(); - mController.attachToDisplayArea(mType, displayId, mOptions); } @Override diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index c9ac245bc36f..1e2f454adeef 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -158,6 +158,21 @@ public class WindowContextController { } } + /** + * Reparents the window context from the current attached display to another. {@code type} and + * {@code options} must be the same as the previous attach call, otherwise this will fail + * silently. + */ + public void reparentToDisplayArea( + @WindowType int type, int displayId, @Nullable Bundle options) { + if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) { + attachToDisplayArea(type, displayId, options); + return; + } + // No need to propagate type and options as this is already attached and they can't change. + getWindowTokenClientController().reparentToDisplayArea(mToken, displayId); + } + /** Gets the {@link WindowTokenClientController}. */ @VisibleForTesting @NonNull diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java index fa345956ec4d..1ec05b65861d 100644 --- a/core/java/android/window/WindowTokenClientController.java +++ b/core/java/android/window/WindowTokenClientController.java @@ -197,6 +197,21 @@ public class WindowTokenClientController { } } + /** + * Reparents a {@link WindowTokenClient} and its associated WindowContainer if there's one. + */ + public void reparentToDisplayArea(@NonNull WindowTokenClient client, int displayId) { + try { + if (!getWindowManagerService().reparentWindowContextToDisplayArea(mAppThread, client, + displayId)) { + Log.e(TAG, + "Didn't succeed reparenting of " + client + " to displayId=" + displayId); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, @NonNull WindowContextInfo info, boolean shouldReportConfigChange) { recordWindowContextToken(client); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 8ebc1ed5ae0a..a04071a5997b 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -449,6 +449,14 @@ flag { } flag { + name: "reparent_window_token_api" + namespace: "lse_desktop_experience" + description: "Allows to reparent a window token to a different display" + is_fixed_read_only: true + bug: "381258683" +} + +flag { name: "enable_desktop_windowing_hsum" namespace: "lse_desktop_experience" description: "Enables HSUM on desktop mode." diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 92f9e6014107..5d4c40853009 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -267,7 +267,9 @@ public class AlertController { return Flags.useWearMaterial3Ui() && CompatChanges.isChangeEnabled(WEAR_MATERIAL3_ALERTDIALOG) && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) - && context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault; + && (context.getThemeResId() == com.android.internal.R.style.Theme_DeviceDefault + || context.getThemeResId() + == com.android.internal.R.style.Theme_DeviceDefault_Dialog_Alert); } static boolean canTextInput(View v) { diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl index f01aa80fab4f..2cfc680a3fe8 100644 --- a/core/java/com/android/internal/app/IAppOpsService.aidl +++ b/core/java/com/android/internal/app/IAppOpsService.aidl @@ -163,5 +163,4 @@ interface IAppOpsService { void finishOperationForDevice(IBinder clientId, int code, int uid, String packageName, @nullable String attributionTag, int virtualDeviceId); List<AppOpsManager.PackageOps> getPackagesForOpsForDevice(in int[] ops, String persistentDeviceId); - oneway void noteOperationsInBatch(in Map batchedNoteOps); } diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags index 66ee131badac..dfec49907c69 100644 --- a/core/java/com/android/internal/jank/EventLogTags.logtags +++ b/core/java/com/android/internal/jank/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.internal.jank; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 1e965c5db7ae..bda7547087ae 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import android.ravenwood.annotation.RavenwoodReplace; import android.util.ArraySet; import android.util.EmptyArray; @@ -39,6 +40,10 @@ import java.util.function.IntFunction; /** * Static utility methods for arrays that aren't already included in {@link java.util.Arrays}. + * <p> + * Test with: + * <code>atest FrameworksUtilTests:com.android.internal.util.ArrayUtilsTest</code> + * <code>atest FrameworksUtilTestsRavenwood:com.android.internal.util.ArrayUtilsTest</code> */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public class ArrayUtils { @@ -85,6 +90,69 @@ public class ArrayUtils { } /** + * This is like <code>new byte[length]</code>, but it allocates the array as non-movable. This + * prevents copies of the data from being left on the Java heap as a result of heap compaction. + * Use this when the array will contain sensitive data such as a password or cryptographic key + * that needs to be wiped from memory when no longer needed. The owner of the array is still + * responsible for the zeroization; {@link #zeroize(byte[])} should be used to do so. + * + * @param length the length of the array to allocate + * @return the new array + */ + public static byte[] newNonMovableByteArray(int length) { + return (byte[]) VMRuntime.getRuntime().newNonMovableArray(byte.class, length); + } + + /** + * Like {@link #newNonMovableByteArray(int)}, but allocates a char array. + * + * @param length the length of the array to allocate + * @return the new array + */ + public static char[] newNonMovableCharArray(int length) { + return (char[]) VMRuntime.getRuntime().newNonMovableArray(char.class, length); + } + + /** + * Zeroizes a byte array as securely as possible. Use this when the array contains sensitive + * data such as a password or cryptographic key. + * <p> + * This zeroizes the array in a way that is guaranteed to not be optimized out by the compiler. + * If supported by the architecture, it zeroizes the data not just in the L1 data cache but also + * in other levels of the memory hierarchy up to and including main memory (but not above that). + * <p> + * This works on any <code>byte[]</code>, but to ensure that copies of the array aren't left on + * the Java heap the array should have been allocated with {@link #newNonMovableByteArray(int)}. + * Use on other arrays might also introduce performance anomalies. + * + * @param array the array to zeroize. If null, this method has no effect. + */ + @RavenwoodReplace public static native void zeroize(byte[] array); + + /** + * Replacement of the above method for host-side unit testing that doesn't support JNI yet. + */ + public static void zeroize$ravenwood(byte[] array) { + if (array != null) { + Arrays.fill(array, (byte) 0); + } + } + + /** + * Like {@link #zeroize(byte[])}, but for char arrays. + */ + @RavenwoodReplace public static native void zeroize(char[] array); + + /** + * Replacement of the above method for host-side unit testing that doesn't support JNI yet. + */ + public static void zeroize$ravenwood(char[] array) { + if (array != null) { + Arrays.fill(array, (char) 0); + } + } + + /** * Checks if the beginnings of two byte arrays are equal. * * @param array1 the first byte array diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java index 38685b652c50..5177a0360032 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java @@ -435,10 +435,15 @@ public final class LocalFloatingToolbarPopup implements FloatingToolbarPopup { private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) { refreshViewPort(); - // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in - // landscape. - final int x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2, - mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth()); + final int x; + if (mPopupWindow.getWidth() > mViewPortOnScreen.width()) { + // Not enough space - prefer to position as far left as possible + x = mViewPortOnScreen.left; + } else { + // Initialize x ensuring that the toolbar isn't rendered behind the system bar insets + x = Math.clamp(contentRectOnScreen.centerX() - mPopupWindow.getWidth() / 2, + mViewPortOnScreen.left, mViewPortOnScreen.right - mPopupWindow.getWidth()); + } final int y; diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java index 1e9ba78e52c5..33f93fccff58 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -699,6 +699,7 @@ public class CoreDocument { } op.markNotDirty(); op.apply(context); + context.incrementOpCount(); } } @@ -1000,6 +1001,16 @@ public class CoreDocument { private final float[] mScaleOutput = new float[2]; private final float[] mTranslateOutput = new float[2]; private int mRepaintNext = -1; // delay to next repaint -1 = don't 1 = asap + private int mLastOpCount; + + /** + * This is the number of ops used to calculate the last frame. + * + * @return number of ops + */ + public int getOpsPerFrame() { + return mLastOpCount; + } /** * Returns > 0 if it needs to repaint @@ -1017,6 +1028,7 @@ public class CoreDocument { * @param theme the theme we want to use for this document. */ public void paint(@NonNull RemoteContext context, int theme) { + context.getLastOpCount(); context.getPaintContext().clearNeedsRepaint(); context.loadFloat(RemoteContext.ID_DENSITY, context.getDensity()); context.mMode = RemoteContext.ContextMode.UNSET; @@ -1027,21 +1039,24 @@ public class CoreDocument { context.mRemoteComposeState = mRemoteComposeState; context.mRemoteComposeState.setContext(context); + // If we have a content sizing set, we are going to take the original document + // dimension into account and apply scale+translate according to the RootContentBehavior + // rules. if (mContentSizing == RootContentBehavior.SIZING_SCALE) { // we need to add canvas transforms ops here computeScale(context.mWidth, context.mHeight, mScaleOutput); - computeTranslate( - context.mWidth, - context.mHeight, - mScaleOutput[0], - mScaleOutput[1], - mTranslateOutput); + float sw = mScaleOutput[0]; + float sh = mScaleOutput[1]; + computeTranslate(context.mWidth, context.mHeight, sw, sh, mTranslateOutput); context.mPaintContext.translate(mTranslateOutput[0], mTranslateOutput[1]); - context.mPaintContext.scale(mScaleOutput[0], mScaleOutput[1]); + context.mPaintContext.scale(sw, sh); + } else { + // If not, we set the document width and height to be the current context width and + // height. + setWidth((int) context.mWidth); + setHeight((int) context.mHeight); } mTimeVariables.updateTime(context); - context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, context.mWidth); - context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, context.mHeight); mRepaintNext = context.updateOps(); if (mRootLayoutComponent != null) { if (context.mWidth != mRootLayoutComponent.getWidth() @@ -1051,11 +1066,11 @@ public class CoreDocument { if (mRootLayoutComponent.needsMeasure()) { mRootLayoutComponent.layout(context); } - // TODO -- this should be specifically about applying animation, not paint - mRootLayoutComponent.paint(context.getPaintContext()); - context.mPaintContext.reset(); - // TODO -- should be able to remove this - mRootLayoutComponent.updateVariables(context); + if (mRootLayoutComponent.needsBoundsAnimation()) { + mRepaintNext = 1; + mRootLayoutComponent.clearNeedsBoundsAnimation(); + mRootLayoutComponent.animatingBounds(context); + } if (DEBUG) { String hierarchy = mRootLayoutComponent.displayHierarchy(); System.out.println(hierarchy); @@ -1081,6 +1096,7 @@ public class CoreDocument { op.markNotDirty(); ((VariableSupport) op).updateVariables(context); } + context.incrementOpCount(); op.apply(context); } } @@ -1093,6 +1109,38 @@ public class CoreDocument { if (DEBUG && mRootLayoutComponent != null) { System.out.println(mRootLayoutComponent.displayHierarchy()); } + mLastOpCount = context.getLastOpCount(); + } + + /** + * Get an estimated number of operations executed in a paint + * + * @return number of operations + */ + public int getNumberOfOps() { + int count = mOperations.size(); + + for (Operation mOperation : mOperations) { + if (mOperation instanceof Component) { + count += getChildOps((Component) mOperation); + } + } + return count; + } + + private int getChildOps(@NonNull Component base) { + int count = base.mList.size(); + for (Operation mOperation : base.mList) { + + if (mOperation instanceof Component) { + int mult = 1; + if (mOperation instanceof LoopOperation) { + mult = ((LoopOperation) mOperation).estimateIterations(); + } + count += mult * getChildOps((Component) mOperation); + } + } + return count; } @NonNull @@ -1116,6 +1164,9 @@ public class CoreDocument { if (mOperation instanceof Component) { Component com = (Component) mOperation; count += addChildren(com, map, buffer); + } else if (mOperation instanceof LoopOperation) { + LoopOperation com = (LoopOperation) mOperation; + count += addChildren(com, map, buffer); } } @@ -1153,6 +1204,35 @@ public class CoreDocument { if (mOperation instanceof Component) { count += addChildren((Component) mOperation, map, tmp); } + if (mOperation instanceof LoopOperation) { + count += addChildren((LoopOperation) mOperation, map, tmp); + } + } + return count; + } + + private int addChildren( + @NonNull LoopOperation base, + @NonNull HashMap<String, int[]> map, + @NonNull WireBuffer tmp) { + int count = base.mList.size(); + for (Operation mOperation : base.mList) { + Class<? extends Operation> c = mOperation.getClass(); + int[] values; + if (map.containsKey(c.getSimpleName())) { + values = map.get(c.getSimpleName()); + } else { + values = new int[2]; + map.put(c.getSimpleName(), values); + } + values[0] += 1; + values[1] += sizeOfComponent(mOperation, tmp); + if (mOperation instanceof Component) { + count += addChildren((Component) mOperation, map, tmp); + } + if (mOperation instanceof LoopOperation) { + count += addChildren((LoopOperation) mOperation, map, tmp); + } } return count; } @@ -1198,7 +1278,6 @@ public class CoreDocument { * @param ctl the call back to allow evaluation of shaders */ public void checkShaders(RemoteContext context, ShaderControl ctl) { - int count = 0; for (Operation op : mOperations) { if (op instanceof TextData) { op.apply(context); diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index c03f44bfc162..b5587ce095a2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -40,6 +40,7 @@ import java.time.ZoneOffset; * <p>We also contain a PaintContext, so that any operation can draw as needed. */ public abstract class RemoteContext { + private static final int MAX_OP_COUNT = 100_000; // Maximum cmds per frame protected @NonNull CoreDocument mDocument = new CoreDocument(); // todo: is this a valid way to initialize? bbade@ public @NonNull RemoteComposeState mRemoteComposeState = @@ -52,6 +53,7 @@ public abstract class RemoteContext { int mDebug = 0; + private int mOpCount; private int mTheme = Theme.UNSPECIFIED; public float mWidth = 0f; @@ -631,4 +633,23 @@ public abstract class RemoteContext { float right, float bottom, int metadataId); + + /** increments the count of operations executed in a pass */ + public void incrementOpCount() { + mOpCount++; + if (mOpCount > MAX_OP_COUNT) { + throw new RuntimeException("Too many operations executed"); + } + } + + /** + * Get the last Op Count and clear the count. + * + * @return the number of ops executed. + */ + public int getLastOpCount() { + int count = mOpCount; + mOpCount = 0; + return count; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java index cd7ebec67a46..ea917db98e65 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java +++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java @@ -24,7 +24,7 @@ import java.time.ZoneOffset; /** This generates the standard system variables for time. */ public class TimeVariables { - private static final float BUILD = 0.01f; + private static final float BUILD = 0.02f; /** * This class populates all time variables in the system diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java index dcf1d250b2f5..e05bdf2b824d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java @@ -106,6 +106,7 @@ public class ClickModifierOperation extends PaintOperation for (Operation op : mList) { if (op instanceof TextData) { op.apply(context); + context.incrementOpCount(); } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java index e95dfdaa4cf9..8a77dc3aafa5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java @@ -66,6 +66,34 @@ public class Component extends PaintOperation implements Measurable, Serializabl protected float mZIndex = 0f; + private boolean mNeedsBoundsAnimation = false; + + /** + * Mark the component as needing a bounds animation pass + */ + public void markNeedsBoundsAnimation() { + mNeedsBoundsAnimation = true; + if (mParent != null && !mParent.mNeedsBoundsAnimation) { + mParent.markNeedsBoundsAnimation(); + } + } + + /** + * Clear the bounds animation pass flag + */ + public void clearNeedsBoundsAnimation() { + mNeedsBoundsAnimation = false; + } + + /** + * True if needs a bounds animation + * + * @return true if needs a bounds animation pass + */ + public boolean needsBoundsAnimation() { + return mNeedsBoundsAnimation; + } + public float getZIndex() { return mZIndex; } @@ -382,12 +410,40 @@ public class Component extends PaintOperation implements Measurable, Serializabl } else { mVisibility = m.getVisibility(); } - setWidth(m.getW()); - setHeight(m.getH()); - setLayoutPosition(m.getX(), m.getY()); + if (mAnimateMeasure == null) { + setWidth(m.getW()); + setHeight(m.getH()); + setLayoutPosition(m.getX(), m.getY()); + updateComponentValues(context); + clearNeedsBoundsAnimation(); + } else { + mAnimateMeasure.apply(context); + updateComponentValues(context); + markNeedsBoundsAnimation(); + } mFirstLayout = false; } + /** + * Animate the bounds of the component as needed + * @param context + */ + public void animatingBounds(@NonNull RemoteContext context) { + if (mAnimateMeasure != null) { + mAnimateMeasure.apply(context); + updateComponentValues(context); + markNeedsBoundsAnimation(); + } else { + clearNeedsBoundsAnimation(); + } + for (Operation op : mList) { + if (op instanceof Measurable) { + Measurable m = (Measurable) op; + m.animatingBounds(context); + } + } + } + @NonNull public float[] locationInWindow = new float[2]; public boolean contains(float x, float y) { @@ -698,9 +754,6 @@ public class Component extends PaintOperation implements Measurable, Serializabl } public void paintingComponent(@NonNull PaintContext context) { - if (!mComponentValues.isEmpty()) { - updateComponentValues(context.getContext()); - } if (mPreTranslate != null) { mPreTranslate.paint(context); } @@ -718,8 +771,10 @@ public class Component extends PaintOperation implements Measurable, Serializabl } if (op instanceof PaintOperation) { ((PaintOperation) op).paint(context); + context.getContext().incrementOpCount(); } else { op.apply(context.getContext()); + context.getContext().incrementOpCount(); } } context.restore(); @@ -728,7 +783,7 @@ public class Component extends PaintOperation implements Measurable, Serializabl public boolean applyAnimationAsNeeded(@NonNull PaintContext context) { if (context.isAnimationEnabled() && mAnimateMeasure != null) { - mAnimateMeasure.apply(context); + mAnimateMeasure.paint(context); context.needsRepaint(); return true; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java index e25392c5d2ff..91038852573e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java @@ -286,7 +286,9 @@ public class LayoutComponent extends Component { @Override public void paintingComponent(@NonNull PaintContext context) { Component prev = context.getContext().mLastComponent; - context.getContext().mLastComponent = this; + RemoteContext remoteContext = context.getContext(); + + remoteContext.mLastComponent = this; context.save(); context.translate(mX, mY); if (context.isVisualDebug()) { @@ -329,6 +331,7 @@ public class LayoutComponent extends Component { child.updateVariables(context.getContext()); child.markNotDirty(); } + remoteContext.incrementOpCount(); child.paint(context); } } else { @@ -337,6 +340,7 @@ public class LayoutComponent extends Component { child.updateVariables(context.getContext()); child.markNotDirty(); } + remoteContext.incrementOpCount(); child.paint(context); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java index 505656e212f1..9fc5da8320ba 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java @@ -57,6 +57,7 @@ public abstract class ListActionsOperation extends PaintOperation for (Operation op : mList) { if (op instanceof TextData) { op.apply(context); + context.incrementOpCount(); } } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java index 1b85681bcc08..ab1e0ac73368 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java @@ -107,9 +107,11 @@ public class LoopOperation extends PaintOperation implements VariableSupport { @Override public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); if (mIndexVariableId == 0) { for (float i = mFromOut; i < mUntilOut; i += mStepOut) { for (Operation op : mList) { + remoteContext.incrementOpCount(); op.apply(context.getContext()); } } @@ -120,6 +122,7 @@ public class LoopOperation extends PaintOperation implements VariableSupport { if (op instanceof VariableSupport && op.isDirty()) { ((VariableSupport) op).updateVariables(context.getContext()); } + remoteContext.incrementOpCount(); op.apply(context.getContext()); } } @@ -172,4 +175,16 @@ public class LoopOperation extends PaintOperation implements VariableSupport { .field(DocumentedOperation.FLOAT, "step", "value step") .field(DocumentedOperation.FLOAT, "until", "stops less than or equal"); } + + /** + * Calculate and estimate of the number of iterations + * + * @return number of loops or 10 if based on variables + */ + public int estimateIterations() { + if (!(Float.isNaN(mUntil) || Float.isNaN(mFrom) || Float.isNaN(mStep))) { + return (int) (0.5f + (mUntil - mFrom) / mStep); + } + return 10; // this is a generic estmate if the values are variables; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java index 11c0f3f394f5..11fa7ee670dd 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java @@ -137,8 +137,8 @@ public class RootLayoutComponent extends Component implements ComponentStartOper return; } context.mLastComponent = this; - mWidth = context.mWidth; - mHeight = context.mHeight; + setWidth(context.mWidth); + setHeight(context.mHeight); // TODO: reuse MeasurePass MeasurePass measurePass = new MeasurePass(); @@ -155,7 +155,9 @@ public class RootLayoutComponent extends Component implements ComponentStartOper @Override public void paint(@NonNull PaintContext context) { mNeedsRepaint = false; - context.getContext().mLastComponent = this; + RemoteContext remoteContext = context.getContext(); + remoteContext.mLastComponent = this; + context.save(); if (mParent == null) { // root layout @@ -165,6 +167,7 @@ public class RootLayoutComponent extends Component implements ComponentStartOper for (Operation op : mList) { if (op instanceof PaintOperation) { ((PaintOperation) op).paint(context); + remoteContext.incrementOpCount(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java index 2af3c739035c..1de956b7e5d7 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent; import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure; @@ -103,13 +104,18 @@ public class AnimateMeasure { @NonNull public PaintBundle paint = new PaintBundle(); - public void apply(@NonNull PaintContext context) { - update(context.getContext().currentTime); - + /** + * Apply the layout portion of the animation if any + * + * @param context + */ + public void apply(@NonNull RemoteContext context) { + update(context.currentTime); mComponent.setX(getX()); mComponent.setY(getY()); mComponent.setWidth(getWidth()); mComponent.setHeight(getHeight()); + mComponent.updateVariables(context); float w = mComponent.getWidth(); float h = mComponent.getHeight(); @@ -120,10 +126,17 @@ public class AnimateMeasure { h -= pop.getTop() + pop.getBottom(); } if (op instanceof DecoratorComponent) { - ((DecoratorComponent) op).layout(context.getContext(), mComponent, w, h); + ((DecoratorComponent) op).layout(context, mComponent, w, h); } } + } + /** + * Paint the transition animation for the component owned + * + * @param context + */ + public void paint(@NonNull PaintContext context) { if (mOriginal.getVisibility() != mTarget.getVisibility()) { if (mTarget.getVisibility() == Component.Visibility.GONE) { switch (mExitAnimation) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java index fbf2784be843..a9998745a5d6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java @@ -44,4 +44,11 @@ public interface Measurable { * @return true if need to remeasured, false otherwise */ boolean needsMeasure(); + + /** + * Animate bounds of the component + * + * @param context + */ + void animatingBounds(RemoteContext context); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java index d2ba13f69a91..a1609ace2138 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java @@ -49,6 +49,7 @@ public class ComponentModifiers extends PaintOperation super.apply(context); for (ModifierOperation op : mList) { op.apply(context); + context.incrementOpCount(); } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index 7dad2931c97f..6eb83f1da410 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -250,8 +250,23 @@ public class RemoteComposePlayer extends FrameLayout { mInner.clearLocalString("SYSTEM:" + name); } + /** + * This is the number of ops used to calculate the last frame. + * + * @return number of ops + */ + public int getOpsPerFrame() { + return mInner.getDocument().mDocument.getOpsPerFrame(); + } + /** Id action callback interface */ public interface IdActionCallbacks { + /** + * Callback for on action + * + * @param id the id of the action + * @param metadata the metadata of the action + */ void onAction(int id, String metadata); } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b39bacdb239f..8e3303a4ddbd 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -92,6 +92,7 @@ cc_library_shared_for_libandroid_runtime { "android_view_VelocityTracker.cpp", "android_view_VerifiedKeyEvent.cpp", "android_view_VerifiedMotionEvent.cpp", + "com_android_internal_util_ArrayUtils.cpp", "com_android_internal_util_VirtualRefBasePtr.cpp", "core_jni_helpers.cpp", ":deviceproductinfoconstants_aidl", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index ac187b08f0f1..78d69f0714e0 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -220,6 +220,7 @@ extern int register_com_android_internal_os_Zygote(JNIEnv *env); extern int register_com_android_internal_os_ZygoteCommandBuffer(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env); extern int register_com_android_internal_security_VerityUtils(JNIEnv* env); +extern int register_com_android_internal_util_ArrayUtils(JNIEnv* env); extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env); extern int register_android_window_WindowInfosListener(JNIEnv* env); extern int register_android_window_ScreenCapture(JNIEnv* env); @@ -1621,6 +1622,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_com_android_internal_os_ZygoteCommandBuffer), REG_JNI(register_com_android_internal_os_ZygoteInit), REG_JNI(register_com_android_internal_security_VerityUtils), + REG_JNI(register_com_android_internal_util_ArrayUtils), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index b2eeff36c007..f40cfd9f8e51 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -532,7 +532,12 @@ static inline bool app_compat_16kb_enabled() { static const size_t kPageSize = getpagesize(); // App compat is only applicable on 16kb-page-size devices. - return kPageSize == 0x4000; + if (kPageSize != 0x4000) { + return false; + } + + // Explicit disabled status for app compat + return !android::base::GetBoolProperty("pm.16kb.app_compat.disabled", false); } static jint diff --git a/core/jni/com_android_internal_util_ArrayUtils.cpp b/core/jni/com_android_internal_util_ArrayUtils.cpp new file mode 100644 index 000000000000..c70625815b90 --- /dev/null +++ b/core/jni/com_android_internal_util_ArrayUtils.cpp @@ -0,0 +1,119 @@ +/* + * 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. + */ + +#define LOG_TAG "ArrayUtils" + +#include <android-base/logging.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <string.h> +#include <unistd.h> +#include <utils/Log.h> + +namespace android { + +static size_t GetCacheLineSize() { + long size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); + if (size <= 0) { + ALOGE("Unable to determine L1 data cache line size. Assuming 32 bytes"); + return 32; + } + // The cache line size should always be a power of 2. + CHECK((size & (size - 1)) == 0); + + return size; +} + +static void CleanCacheLineContainingAddress(const uint8_t* p) { +#if defined(__aarch64__) + // 'dc cvac' stands for "Data Cache line Clean by Virtual Address to point-of-Coherency". + // It writes the cache line back to the "point-of-coherency", i.e. main memory. + asm volatile("dc cvac, %0" ::"r"(p)); +#elif defined(__i386__) || defined(__x86_64__) + asm volatile("clflush (%0)" ::"r"(p)); +#elif defined(__riscv) + // This should eventually work, but it is not ready to be enabled yet: + // 1.) The Android emulator needs to add support for zicbom. + // 2.) Kernel needs to enable zicbom in usermode. + // 3.) Android clang needs to add zicbom to the target. + // asm volatile("cbo.clean (%0)" ::"r"(p)); +#elif defined(__arm__) + // arm32 has a cacheflush() syscall, but it is undocumented and only flushes the icache. + // It is not the same as cacheflush(2) as documented in the Linux man-pages project. +#else +#error "Unknown architecture" +#endif +} + +static void CleanDataCache(const uint8_t* p, size_t buffer_size, size_t cache_line_size) { + // Clean the first line that overlaps the buffer. + CleanCacheLineContainingAddress(p); + // Clean any additional lines that overlap the buffer. Use cache-line-aligned addresses to + // ensure that (a) the last cache line gets flushed, and (b) no cache line is flushed twice. + for (size_t i = cache_line_size - ((uintptr_t)p & (cache_line_size - 1)); i < buffer_size; + i += cache_line_size) { + CleanCacheLineContainingAddress(p + i); + } +} + +static void ZeroizePrimitiveArray(JNIEnv* env, jclass clazz, jarray array, size_t component_len) { + static const size_t cache_line_size = GetCacheLineSize(); + + if (array == nullptr) { + return; + } + + size_t buffer_size = env->GetArrayLength(array) * component_len; + if (buffer_size == 0) { + return; + } + + // ART guarantees that GetPrimitiveArrayCritical never copies. + jboolean isCopy; + void* elems = env->GetPrimitiveArrayCritical(array, &isCopy); + CHECK(!isCopy); + +#ifdef __BIONIC__ + memset_explicit(elems, 0, buffer_size); +#else + memset(elems, 0, buffer_size); +#endif + // Clean the data cache so that the data gets zeroized in main memory right away. Without this, + // it might not be written to main memory until the cache line happens to be evicted. + CleanDataCache(static_cast<const uint8_t*>(elems), buffer_size, cache_line_size); + + env->ReleasePrimitiveArrayCritical(array, elems, /* mode= */ 0); +} + +static void ZeroizeByteArray(JNIEnv* env, jclass clazz, jbyteArray array) { + ZeroizePrimitiveArray(env, clazz, array, sizeof(jbyte)); +} + +static void ZeroizeCharArray(JNIEnv* env, jclass clazz, jcharArray array) { + ZeroizePrimitiveArray(env, clazz, array, sizeof(jchar)); +} + +static const JNINativeMethod sMethods[] = { + {"zeroize", "([B)V", (void*)ZeroizeByteArray}, + {"zeroize", "([C)V", (void*)ZeroizeCharArray}, +}; + +int register_com_android_internal_util_ArrayUtils(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/internal/util/ArrayUtils", sMethods, + NELEM(sMethods)); +} + +} // namespace android diff --git a/core/res/Android.bp b/core/res/Android.bp index 903d08b9a2ab..be4fb8bdecfb 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -179,6 +179,7 @@ android_app { "art-aconfig-flags", "ranging_aconfig_flags", "aconfig_settingslib_flags", + "telephony_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f175e93ea226..c82978d88ea0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7042,6 +7042,13 @@ <permission android:name="android.permission.MANAGE_SUBSCRIPTION_PLANS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide Allows for reading subscription plan fields for status and end date. + @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE) + --> + <permission android:name="android.permission.READ_SUBSCRIPTION_PLANS" + android:protectionLevel="signature|privileged" + android:featureFlag="com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date" /> + <!-- C2DM permission. @hide Used internally. --> diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml index 09c02c9994f4..76c810bdb2c1 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_base.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml @@ -28,8 +28,8 @@ android:id="@+id/left_icon" android:layout_width="@dimen/notification_2025_left_icon_size" android:layout_height="@dimen/notification_2025_left_icon_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_left_icon_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" @@ -41,8 +41,8 @@ android:id="@+id/icon" android:layout_width="@dimen/notification_2025_icon_circle_size" android:layout_height="@dimen/notification_2025_icon_circle_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_icon_circle_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_2025_icon_circle_padding" android:maxDrawableWidth="@dimen/notification_2025_icon_circle_size" diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml index f539105368e7..2e0a7afc3cd1 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_media.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml @@ -32,8 +32,8 @@ android:id="@+id/left_icon" android:layout_width="@dimen/notification_2025_left_icon_size" android:layout_height="@dimen/notification_2025_left_icon_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_left_icon_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" @@ -45,8 +45,8 @@ android:id="@+id/icon" android:layout_width="@dimen/notification_2025_icon_circle_size" android:layout_height="@dimen/notification_2025_icon_circle_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_icon_circle_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_2025_icon_circle_padding" /> diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml index ddf3ebceaa46..f644adefda9d 100644 --- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml +++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml @@ -46,8 +46,8 @@ android:id="@+id/left_icon" android:layout_width="@dimen/notification_2025_left_icon_size" android:layout_height="@dimen/notification_2025_left_icon_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_left_icon_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" @@ -59,8 +59,8 @@ android:id="@+id/icon" android:layout_width="@dimen/notification_2025_icon_circle_size" android:layout_height="@dimen/notification_2025_icon_circle_size" - android:layout_gravity="center_vertical|start" - android:layout_marginStart="@dimen/notification_icon_circle_start" + android:layout_alignParentStart="true" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_2025_icon_circle_padding" /> diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml index b7fe454e09d4..63872aff8dd0 100644 --- a/core/res/res/layout/notification_2025_template_header.xml +++ b/core/res/res/layout/notification_2025_template_header.xml @@ -33,8 +33,7 @@ android:layout_width="@dimen/notification_2025_left_icon_size" android:layout_height="@dimen/notification_2025_left_icon_size" android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_marginStart="@dimen/notification_left_icon_start" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_large_icon_outline" android:clipToOutline="true" android:importantForAccessibility="no" @@ -47,8 +46,7 @@ android:layout_width="@dimen/notification_2025_icon_circle_size" android:layout_height="@dimen/notification_2025_icon_circle_size" android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_marginStart="@dimen/notification_icon_circle_start" + android:layout_margin="@dimen/notification_2025_margin" android:background="@drawable/notification_icon_circle" android:padding="@dimen/notification_2025_icon_circle_padding" android:maxDrawableWidth="@dimen/notification_2025_icon_circle_size" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 13c125cb7349..53b47622e8ae 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4228,13 +4228,12 @@ must match the value of config_cameraLaunchGestureSensorType in OEM's HAL --> <string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string> - <!-- Allow the gesture to double tap the power button to trigger a target action. --> - <bool name="config_doubleTapPowerGestureEnabled">true</bool> <!-- Allow the gesture to double tap the power button twice to start the camera while the device is non-interactive. --> <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> - <!-- Allow the gesture to double tap the power button twice to launch the wallet. --> - <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool> + + <!-- Allow the gesture to double tap the power button to trigger a target action. --> + <bool name="config_doubleTapPowerGestureEnabled">true</bool> <!-- Default target action for double tap of the power button gesture. 0: Launch camera 1: Launch wallet --> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index bb76b9fae8d7..196da29127df 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -496,4 +496,8 @@ not connected state. --> <bool name="config_satellite_allow_check_message_in_not_connected">false</bool> <java-symbol type="bool" name="config_satellite_allow_check_message_in_not_connected" /> + + <!-- Whether to allow TN scanning during satellite session. --> + <bool name="config_satellite_allow_tn_scanning_during_satellite_session">true</bool> + <java-symbol type="bool" name="config_satellite_allow_tn_scanning_during_satellite_session" /> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7e8a2e9f2c99..28de553f6063 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3161,9 +3161,8 @@ <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" /> <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" /> <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" /> - <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" /> <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> - <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" /> + <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" /> <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" /> <java-symbol type="bool" name="config_emergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 63e678d9ee53..9effeec23890 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -467,12 +467,25 @@ public class NotificationTest { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); assertThat(n.hasPromotableCharacteristics()).isTrue(); } @Test @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) + public void testHasPromotableCharacteristics_notOngoing() { + Notification n = new Notification.Builder(mContext, "test") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + assertThat(n.hasPromotableCharacteristics()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_UI_RICH_ONGOING) public void testHasPromotableCharacteristics_wrongStyle() { Notification n = new Notification.Builder(mContext, "test") .setSmallIcon(android.R.drawable.sym_def_app_icon) @@ -480,6 +493,7 @@ public class NotificationTest { .setContentTitle("TITLE") .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); assertThat(n.hasPromotableCharacteristics()).isFalse(); } @@ -491,6 +505,7 @@ public class NotificationTest { .setSmallIcon(android.R.drawable.sym_def_app_icon) .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) + .setOngoing(true) .build(); assertThat(n.hasPromotableCharacteristics()).isFalse(); } @@ -503,6 +518,7 @@ public class NotificationTest { .setStyle(new Notification.BigTextStyle()) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); assertThat(n.hasPromotableCharacteristics()).isFalse(); } @@ -515,6 +531,7 @@ public class NotificationTest { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setGroup("someGroup") .setGroupSummary(true) .build(); diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java index 177c7f0b2f27..bd273377984d 100644 --- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java +++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java @@ -740,5 +740,20 @@ public class PropertyInvalidatedCacheTests { assertEquals(null, cache.query(30)); // The recompute is 4 because nulls were not cached. assertEquals(4, cache.getRecomputeCount()); + + // Verify that the default is not to cache nulls. + cache = new TestCache(new Args(MODULE_TEST) + .maxEntries(4).api("testCachingNulls"), + new TestQuery()); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + // The recompute is 4 because nulls were not cached. + assertEquals(4, cache.getRecomputeCount()); } } diff --git a/core/tests/coretests/src/android/app/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java index 230c9e86db6b..6bd9b6a1453f 100644 --- a/core/tests/coretests/src/android/app/QueuedWorkTest.java +++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java @@ -163,18 +163,18 @@ public class QueuedWorkTest { @Test public void testHasPendingWork() { - Semaphore releaser = new Semaphore(0); - mQueuedWork.queue( - () -> { - try { - releaser.acquire(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }, false); + final Semaphore releaser1 = new Semaphore(0); + final Semaphore releaser2 = new Semaphore(0); + mQueuedWork.queue(() -> releaser1.acquireUninterruptibly(), false); + mQueuedWork.queue(() -> releaser2.release(), false); + // Worker should be waiting for releaser1, + // and have pending work to release releaser2 assertThat(mQueuedWork.hasPendingWork()).isTrue(); - releaser.release(); - mQueuedWork.waitToFinish(); + + // Allow worker to get to releasing releaser2 + releaser1.release(); + releaser2.acquireUninterruptibly(); + // If we got here then there is no pending work. assertThat(mQueuedWork.hasPendingWork()).isFalse(); } }
\ No newline at end of file diff --git a/core/tests/coretests/src/android/os/IpcDataCacheTest.java b/core/tests/coretests/src/android/os/IpcDataCacheTest.java index e14608ac503d..bb8356f7aebe 100644 --- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java +++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java @@ -16,13 +16,21 @@ package android.os; +import static android.app.Flags.FLAG_PIC_CACHE_NULLS; +import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import android.app.PropertyInvalidatedCache; +import android.app.PropertyInvalidatedCache.Args; import android.multiuser.Flags; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.ravenwood.RavenwoodRule; +import android.os.IpcDataCache; import androidx.test.filters.SmallTest; @@ -43,6 +51,10 @@ import org.junit.Test; @SmallTest public class IpcDataCacheTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + // Configuration for creating caches private static final String MODULE = IpcDataCache.MODULE_TEST; private static final String API = "testApi"; @@ -287,7 +299,12 @@ public class IpcDataCacheTest { @Override public String apply(Integer qv) { mRecomputeCount += 1; - return "foo" + qv.toString(); + // Special case for testing caches of nulls. Integers in the range 30-40 return null. + if (qv >= 30 && qv < 40) { + return null; + } else { + return "foo" + qv.toString(); + } } int getRecomputeCount() { @@ -406,31 +423,16 @@ public class IpcDataCacheTest { } @Test - public void testConfig() { + public void testConfigDisable() { + // Create a set of caches based on a set of chained configs. IpcDataCache.Config a = new IpcDataCache.Config(8, MODULE, "apiA"); TestCache ac = new TestCache(a); - assertEquals(8, a.maxEntries()); - assertEquals(MODULE, a.module()); - assertEquals("apiA", a.api()); - assertEquals("apiA", a.name()); IpcDataCache.Config b = new IpcDataCache.Config(a, "apiB"); TestCache bc = new TestCache(b); - assertEquals(8, b.maxEntries()); - assertEquals(MODULE, b.module()); - assertEquals("apiB", b.api()); - assertEquals("apiB", b.name()); IpcDataCache.Config c = new IpcDataCache.Config(a, "apiC", "nameC"); TestCache cc = new TestCache(c); - assertEquals(8, c.maxEntries()); - assertEquals(MODULE, c.module()); - assertEquals("apiC", c.api()); - assertEquals("nameC", c.name()); IpcDataCache.Config d = a.child("nameD"); TestCache dc = new TestCache(d); - assertEquals(8, d.maxEntries()); - assertEquals(MODULE, d.module()); - assertEquals("apiA", d.api()); - assertEquals("nameD", d.name()); a.disableForCurrentProcess(); assertEquals(ac.isDisabled(), true); @@ -449,6 +451,7 @@ public class IpcDataCacheTest { assertEquals(ec.isDisabled(), true); } + // Verify that invalidating the cache from an app process would fail due to lack of permissions. @Test @android.platform.test.annotations.DisabledOnRavenwood( @@ -507,4 +510,47 @@ public class IpcDataCacheTest { // Re-enable test mode (so that the cleanup for the test does not throw). IpcDataCache.setTestMode(true); } + + @RequiresFlagsEnabled(FLAG_PIC_CACHE_NULLS) + @Test + public void testCachingNulls() { + IpcDataCache.Config c = + new IpcDataCache.Config(4, IpcDataCache.MODULE_TEST, "testCachingNulls"); + TestCache cache; + cache = new TestCache(c.cacheNulls(true)); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + + cache = new TestCache(c.cacheNulls(false)); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + // The recompute is 4 because nulls were not cached. + assertEquals(4, cache.getRecomputeCount()); + + // Verify that the default is not to cache nulls. + cache = new TestCache(c); + cache.invalidateCache(); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + assertEquals(3, cache.getRecomputeCount()); + assertEquals("foo1", cache.query(1)); + assertEquals("foo2", cache.query(2)); + assertEquals(null, cache.query(30)); + // The recompute is 4 because nulls were not cached. + assertEquals(4, cache.getRecomputeCount()); + } } diff --git a/core/tests/coretests/src/android/os/TestLooperManagerTest.java b/core/tests/coretests/src/android/os/TestLooperManagerTest.java deleted file mode 100644 index 4d64a3a94b41..000000000000 --- a/core/tests/coretests/src/android/os/TestLooperManagerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.platform.test.ravenwood.RavenwoodRule; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@RunWith(AndroidJUnit4.class) -public class TestLooperManagerTest { - private static final String TAG = "TestLooperManagerTest"; - - @Rule - public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() - .setProvideMainThread(true) - .build(); - - @Test - public void testMainThread() throws Exception { - doTest(Looper.getMainLooper()); - } - - @Test - public void testCustomThread() throws Exception { - final HandlerThread thread = new HandlerThread(TAG); - thread.start(); - doTest(thread.getLooper()); - } - - private void doTest(Looper looper) throws Exception { - final TestLooperManager tlm = - InstrumentationRegistry.getInstrumentation().acquireLooperManager(looper); - - final Handler handler = new Handler(looper); - final CountDownLatch latch = new CountDownLatch(1); - - assertFalse(tlm.hasMessages(handler, null, 42)); - - handler.sendEmptyMessage(42); - handler.post(() -> { - latch.countDown(); - }); - assertTrue(tlm.hasMessages(handler, null, 42)); - assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); - - final Message first = tlm.next(); - assertEquals(42, first.what); - assertNull(first.callback); - tlm.execute(first); - assertFalse(tlm.hasMessages(handler, null, 42)); - assertFalse(latch.await(100, TimeUnit.MILLISECONDS)); - tlm.recycle(first); - - final Message second = tlm.next(); - assertNotNull(second.callback); - tlm.execute(second); - assertFalse(tlm.hasMessages(handler, null, 42)); - assertTrue(latch.await(100, TimeUnit.MILLISECONDS)); - tlm.recycle(second); - - tlm.release(); - } -} diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index 21930d17ed68..b9344261cade 100644 --- a/core/tests/coretests/src/android/window/WindowContextTest.java +++ b/core/tests/coretests/src/android/window/WindowContextTest.java @@ -33,7 +33,9 @@ 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; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Activity; import android.app.EmptyActivity; @@ -48,7 +50,9 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.IBinder; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.view.Display; import android.view.IWindowManager; import android.view.View; @@ -64,6 +68,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import com.android.frameworks.coretests.R; +import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -91,6 +96,8 @@ public class WindowContextTest { public ActivityTestRule<EmptyActivity> mActivityRule = new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */, false /* launchActivity */); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); private final WindowContext mWindowContext = createWindowContext(); @@ -340,17 +347,35 @@ public class WindowContextTest { } @Test - public void updateDisplay_wasAttached_detachThenAttachedPropagatedToTokenController() { + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentToDisplayId_wasAttached_reparentToDisplayAreaPropagatedToTokenController() { + final WindowTokenClientController mockWindowTokenClientController = + mock(WindowTokenClientController.class); + when(mockWindowTokenClientController.attachToDisplayArea(any(), anyInt(), anyInt(), + any())).thenReturn(true); + WindowTokenClientController.overrideForTesting(mockWindowTokenClientController); + + mWindowContext.reparentToDisplay(DEFAULT_DISPLAY + 1); + + verify(mockWindowTokenClientController).reparentToDisplayArea(any(), + /* displayId= */ eq(DEFAULT_DISPLAY + 1) + ); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentToDisplayId_sameDisplayId_noReparenting() { final WindowTokenClientController mockWindowTokenClientController = mock(WindowTokenClientController.class); + when(mockWindowTokenClientController.attachToDisplayArea(any(), anyInt(), anyInt(), + any())).thenReturn(true); WindowTokenClientController.overrideForTesting(mockWindowTokenClientController); - mWindowContext.updateDisplay(DEFAULT_DISPLAY + 1); + mWindowContext.reparentToDisplay(DEFAULT_DISPLAY); - verify(mockWindowTokenClientController).detachIfNeeded(any()); - verify(mockWindowTokenClientController).attachToDisplayArea(any(), - anyInt(), /* displayId= */ eq(DEFAULT_DISPLAY + 1), - any()); + verify(mockWindowTokenClientController, never()).reparentToDisplayArea(any(), + /* displayId= */ eq(DEFAULT_DISPLAY) + ); } private WindowContext createWindowContext() { diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java index 3b9f35b1eb68..e6586b3c2583 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java @@ -496,4 +496,58 @@ public class ArrayUtilsTest { // expected } } + + // Note: the zeroize() tests only test the behavior that can be tested from a Java test. + // They do not verify that no copy of the data is left anywhere. + + @Test + @SmallTest + public void testZeroizeNonMovableByteArray() { + final int length = 10; + byte[] array = ArrayUtils.newNonMovableByteArray(length); + assertArrayEquals(array, new byte[length]); + Arrays.fill(array, (byte) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new byte[length]); + } + + @Test + @SmallTest + public void testZeroizeRegularByteArray() { + final int length = 10; + byte[] array = new byte[length]; + assertArrayEquals(array, new byte[length]); + Arrays.fill(array, (byte) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new byte[length]); + } + + @Test + @SmallTest + public void testZeroizeNonMovableCharArray() { + final int length = 10; + char[] array = ArrayUtils.newNonMovableCharArray(length); + assertArrayEquals(array, new char[length]); + Arrays.fill(array, (char) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new char[length]); + } + + @Test + @SmallTest + public void testZeroizeRegularCharArray() { + final int length = 10; + char[] array = new char[length]; + assertArrayEquals(array, new char[length]); + Arrays.fill(array, (char) 0xff); + ArrayUtils.zeroize(array); + assertArrayEquals(array, new char[length]); + } + + @Test + @SmallTest + public void testZeroize_acceptsNull() { + ArrayUtils.zeroize((byte[]) null); + ArrayUtils.zeroize((char[]) null); + } } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 9bf4d65e1865..2e885145819c 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -34,6 +34,7 @@ import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; +import android.graphics.fonts.FontStyle; import android.graphics.fonts.FontVariationAxis; import android.graphics.text.TextRunShaper; import android.os.Build; @@ -2141,6 +2142,14 @@ public class Paint { * @see FontVariationAxis */ public boolean setFontVariationSettings(String fontVariationSettings) { + return setFontVariationSettings(fontVariationSettings, 0 /* wght adjust */); + } + + /** + * Set font variation settings with weight adjustment + * @hide + */ + public boolean setFontVariationSettings(String fontVariationSettings, int wghtAdjust) { final boolean useFontVariationStore = Flags.typefaceRedesignReadonly() && CompatChanges.isChangeEnabled(NEW_FONT_VARIATION_MANAGEMENT); if (useFontVariationStore) { @@ -2154,8 +2163,13 @@ public class Paint { long builderPtr = nCreateFontVariationBuilder(axes.length); for (int i = 0; i < axes.length; ++i) { - nAddFontVariationToBuilder(builderPtr, axes[i].getOpenTypeTagValue(), - axes[i].getStyleValue()); + int tag = axes[i].getOpenTypeTagValue(); + float value = axes[i].getStyleValue(); + if (tag == 0x77676874 /* wght */) { + value = Math.clamp(value + wghtAdjust, + FontStyle.FONT_WEIGHT_MIN, FontStyle.FONT_WEIGHT_MAX); + } + nAddFontVariationToBuilder(builderPtr, tag, value); } nSetFontVariationOverride(mNativePaint, builderPtr); mFontVariationSettings = fontVariationSettings; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index 9ea2943bc6da..f0613cec6a0b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -398,27 +398,23 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { new TaskFragmentAnimationParams.Builder(); final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes); builder.setAnimationBackgroundColor(animationBackgroundColor); - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final int openAnimationResId = - splitAttributes.getAnimationParams().getOpenAnimationResId(); - builder.setOpenAnimationResId(openAnimationResId); - final int closeAnimationResId = - splitAttributes.getAnimationParams().getCloseAnimationResId(); - builder.setCloseAnimationResId(closeAnimationResId); - final int changeAnimationResId = - splitAttributes.getAnimationParams().getChangeAnimationResId(); - builder.setChangeAnimationResId(changeAnimationResId); - } + final int openAnimationResId = + splitAttributes.getAnimationParams().getOpenAnimationResId(); + builder.setOpenAnimationResId(openAnimationResId); + final int closeAnimationResId = + splitAttributes.getAnimationParams().getCloseAnimationResId(); + builder.setCloseAnimationResId(closeAnimationResId); + final int changeAnimationResId = + splitAttributes.getAnimationParams().getChangeAnimationResId(); + builder.setChangeAnimationResId(changeAnimationResId); return builder.build(); } @ColorInt private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) { int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR; - AnimationBackground animationBackground = splitAttributes.getAnimationBackground(); - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - animationBackground = splitAttributes.getAnimationParams().getAnimationBackground(); - } + final AnimationBackground animationBackground = + splitAttributes.getAnimationParams().getAnimationBackground(); if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) { animationBackgroundColor = colorBackground.getColor(); } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index b38d00da6dfa..1d0c5057c77f 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -602,8 +602,72 @@ class BubblePositionerTest { testGetBubbleBarExpandedViewBounds(onLeft = false, isOverflow = true) } + @Test + fun getExpandedViewContainerPadding_largeScreen_fitsMaxViewWidth() { + val expandedViewWidth = context.resources.getDimensionPixelSize( + R.dimen.bubble_expanded_view_largescreen_width + ) + // set the screen size so that it is wide enough to fit the maximum width size + val screenWidth = expandedViewWidth * 2 + positioner.update( + defaultDeviceConfig.copy( + windowBounds = Rect(0, 0, screenWidth, 2000), + isLargeScreen = true, + isLandscape = false + ) + ) + val paddings = + positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false) + + val padding = context.resources.getDimensionPixelSize( + R.dimen.bubble_expanded_view_largescreen_landscape_padding + ) + val right = screenWidth - expandedViewWidth - padding + assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, right, 0)) + } + + @Test + fun getExpandedViewContainerPadding_largeScreen_doesNotFitMaxViewWidth() { + positioner.update( + defaultDeviceConfig.copy( + windowBounds = Rect(0, 0, 600, 2000), + isLargeScreen = true, + isLandscape = false + ) + ) + val paddings = + positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false) + + val padding = context.resources.getDimensionPixelSize( + R.dimen.bubble_expanded_view_largescreen_landscape_padding + ) + // the screen is not wide enough to fit the maximum width size, so the view fills the screen + // minus left and right padding + assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0)) + } + + @Test + fun getExpandedViewContainerPadding_smallTablet() { + val screenWidth = 500 + positioner.update( + defaultDeviceConfig.copy( + windowBounds = Rect(0, 0, screenWidth, 2000), + isLargeScreen = true, + isSmallTablet = true, + isLandscape = false + ) + ) + val paddings = + positioner.getExpandedViewContainerPadding(/* onLeft= */ true, /* isOverflow= */ false) + + // for small tablets, the view width is set to be 0.72 * screen width + val viewWidth = (screenWidth * 0.72).toInt() + val padding = (screenWidth - viewWidth) / 2 + assertThat(paddings).isEqualTo(intArrayOf(padding - positioner.pointerSize, 0, padding, 0)) + } + private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) { - positioner.setShowingInBubbleBar(true) + positioner.isShowingInBubbleBar = true val windowBounds = Rect(0, 0, 2000, 2600) val insets = Insets.of(10, 20, 5, 15) val deviceConfig = diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt new file mode 100644 index 000000000000..0d8f80935f5a --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt @@ -0,0 +1,228 @@ +/* + * 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.wm.shell.bubbles.bar + +import android.content.Context +import android.graphics.Insets +import android.graphics.Rect +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.FrameLayout +import androidx.core.animation.AnimatorTestRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.bubbles.Bubble +import com.android.wm.shell.bubbles.BubbleExpandedViewManager +import com.android.wm.shell.bubbles.BubbleLogger +import com.android.wm.shell.bubbles.BubbleOverflow +import com.android.wm.shell.bubbles.BubblePositioner +import com.android.wm.shell.bubbles.DeviceConfig +import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager +import com.android.wm.shell.bubbles.FakeBubbleFactory +import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Semaphore +import java.util.concurrent.TimeUnit +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Test +import org.junit.runner.RunWith + +/** Tests for [BubbleBarAnimationHelper] */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleBarAnimationHelperTest { + + companion object { + @JvmField @ClassRule val animatorTestRule: AnimatorTestRule = AnimatorTestRule() + + const val SCREEN_WIDTH = 2000 + const val SCREEN_HEIGHT = 1000 + } + + private val context = ApplicationProvider.getApplicationContext<Context>() + + private lateinit var animationHelper: BubbleBarAnimationHelper + private lateinit var bubblePositioner: BubblePositioner + private lateinit var expandedViewManager: BubbleExpandedViewManager + private lateinit var bubbleLogger: BubbleLogger + private lateinit var mainExecutor: TestShellExecutor + private lateinit var bgExecutor: TestShellExecutor + private lateinit var container: FrameLayout + + @Before + fun setUp() { + ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() + val windowManager = context.getSystemService(WindowManager::class.java) + bubblePositioner = BubblePositioner(context, windowManager) + bubblePositioner.setShowingInBubbleBar(true) + val deviceConfig = + DeviceConfig( + windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), + isLargeScreen = true, + isSmallTablet = false, + isLandscape = true, + isRtl = false, + insets = Insets.of(10, 20, 30, 40), + ) + bubblePositioner.update(deviceConfig) + expandedViewManager = FakeBubbleExpandedViewManager() + bubbleLogger = BubbleLogger(UiEventLoggerFake()) + + mainExecutor = TestShellExecutor() + bgExecutor = TestShellExecutor() + + container = FrameLayout(context) + + animationHelper = BubbleBarAnimationHelper(context, bubblePositioner) + } + + @After + fun tearDown() { + bgExecutor.flushAll() + mainExecutor.flushAll() + } + + @Test + fun animateSwitch_bubbleToBubble_oldHiddenNewShown() { + val fromBubble = createBubble(key = "from").initialize(container) + val toBubble = createBubble(key = "to").initialize(container) + + val semaphore = Semaphore(0) + val after = Runnable { semaphore.release() } + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(fromBubble, toBubble, after) + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(fromBubble.bubbleBarExpandedView?.visibility).isEqualTo(View.INVISIBLE) + assertThat(fromBubble.bubbleBarExpandedView?.alpha).isEqualTo(0f) + assertThat(fromBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() + + assertThat(toBubble.bubbleBarExpandedView?.visibility).isEqualTo(View.VISIBLE) + assertThat(toBubble.bubbleBarExpandedView?.alpha).isEqualTo(1f) + assertThat(toBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() + } + + @Test + fun animateSwitch_bubbleToBubble_handleColorTransferred() { + val fromBubble = createBubble(key = "from").initialize(container) + fromBubble.bubbleBarExpandedView!! + .handleView + .updateHandleColor(/* isRegionDark= */ true, /* animated= */ false) + val toBubble = createBubble(key = "to").initialize(container) + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(fromBubble, toBubble, /* afterAnimation= */ null) + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + + assertThat(toBubble.bubbleBarExpandedView!!.handleView.handleColor) + .isEqualTo(fromBubble.bubbleBarExpandedView!!.handleView.handleColor) + } + + @Test + fun animateSwitch_bubbleToOverflow_oldHiddenNewShown() { + val fromBubble = createBubble(key = "from").initialize(container) + val overflow = createOverflow().initialize(container) + + val semaphore = Semaphore(0) + val after = Runnable { semaphore.release() } + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(fromBubble, overflow, after) + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(fromBubble.bubbleBarExpandedView?.visibility).isEqualTo(View.INVISIBLE) + assertThat(fromBubble.bubbleBarExpandedView?.alpha).isEqualTo(0f) + assertThat(fromBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() + + assertThat(overflow.bubbleBarExpandedView?.visibility).isEqualTo(View.VISIBLE) + assertThat(overflow.bubbleBarExpandedView?.alpha).isEqualTo(1f) + } + + @Test + fun animateSwitch_overflowToBubble_oldHiddenNewShown() { + val overflow = createOverflow().initialize(container) + val toBubble = createBubble(key = "to").initialize(container) + + val semaphore = Semaphore(0) + val after = Runnable { semaphore.release() } + + getInstrumentation().runOnMainSync { + animationHelper.animateSwitch(overflow, toBubble, after) + animatorTestRule.advanceTimeBy(1000) + } + getInstrumentation().waitForIdleSync() + + assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue() + assertThat(overflow.bubbleBarExpandedView?.visibility).isEqualTo(View.INVISIBLE) + assertThat(overflow.bubbleBarExpandedView?.alpha).isEqualTo(0f) + + assertThat(toBubble.bubbleBarExpandedView?.visibility).isEqualTo(View.VISIBLE) + assertThat(toBubble.bubbleBarExpandedView?.alpha).isEqualTo(1f) + assertThat(toBubble.bubbleBarExpandedView?.isSurfaceZOrderedOnTop).isFalse() + } + + private fun createBubble(key: String): Bubble { + val bubbleBarExpandedView = + FakeBubbleFactory.createExpandedView( + context, + bubblePositioner, + expandedViewManager, + FakeBubbleTaskViewFactory(context, mainExecutor).create(), + mainExecutor, + bgExecutor, + bubbleLogger, + ) + val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView) + return FakeBubbleFactory.createChatBubble(context, key, viewInfo) + } + + private fun createOverflow(): BubbleOverflow { + val overflow = BubbleOverflow(context, bubblePositioner) + overflow.initializeForBubbleBar(expandedViewManager, bubblePositioner, bubbleLogger) + return overflow + } + + private fun Bubble.initialize(container: ViewGroup): Bubble { + getInstrumentation().runOnMainSync { container.addView(bubbleBarExpandedView) } + // Mark taskView's visible + bubbleBarExpandedView!!.onContentVisibilityChanged(true) + return this + } + + private fun BubbleOverflow.initialize(container: ViewGroup): BubbleOverflow { + getInstrumentation().runOnMainSync { container.addView(bubbleBarExpandedView) } + return this + } +} diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index 04c17e54d11f..4c77eaf80a29 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -91,6 +91,9 @@ public class DesktopModeStatus { /** The maximum override density allowed for tasks inside the desktop. */ private static final int DESKTOP_DENSITY_MAX = 1000; + /** The number of [WindowDecorViewHost] instances to warm up on system start. */ + private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2; + /** * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. @@ -122,6 +125,14 @@ public class DesktopModeStatus { private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit"; /** + * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start. + * + * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used. + */ + private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP = + "persist.wm.debug.desktop_window_decor_pre_warm_size"; + + /** * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. */ public static boolean isVeiledResizeEnabled() { @@ -162,6 +173,27 @@ public class DesktopModeStatus { } /** + * Return the maximum size of the window decoration surface control view host pool, or zero if + * there should be no pooling. + */ + public static int getWindowDecorScvhPoolSize(@NonNull Context context) { + if (!Flags.enableDesktopWindowingScvhCacheBugFix()) return 0; + final int maxTaskLimit = getMaxTaskLimit(context); + if (maxTaskLimit > 0) { + return maxTaskLimit; + } + // TODO: b/368032552 - task limit equal to 0 means unlimited. Figure out what the pool + // size should be in that case. + return 0; + } + + /** The number of [WindowDecorViewHost] instances to warm up on system start. */ + public static int getWindowDecorPreWarmSize() { + return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP, + WINDOW_DECOR_PRE_WARM_SIZE); + } + + /** * Return {@code true} if the current device supports desktop mode. */ @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index d2cef4baf798..f269b3831aab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -268,10 +268,7 @@ class ActivityEmbeddingAnimationRunner { final Animation animation = animationProvider.get(info, change, openingWholeScreenBounds); if (shouldUseJumpCutForAnimation(animation)) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - return new ArrayList<>(); - } - continue; + return new ArrayList<>(); } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( info, change, animation, openingWholeScreenBounds); @@ -296,10 +293,7 @@ class ActivityEmbeddingAnimationRunner { final Animation animation = animationProvider.get(info, change, closingWholeScreenBounds); if (shouldUseJumpCutForAnimation(animation)) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - return new ArrayList<>(); - } - continue; + return new ArrayList<>(); } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( info, change, animation, closingWholeScreenBounds); @@ -455,11 +449,9 @@ class ActivityEmbeddingAnimationRunner { final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds); // Jump cut if either animation has zero for duration. - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - for (Animation animation : animations) { - if (shouldUseJumpCutForAnimation(animation)) { - return new ArrayList<>(); - } + for (Animation animation : animations) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); } } // Keep track as we might need to add background color for the animation. @@ -516,10 +508,8 @@ class ActivityEmbeddingAnimationRunner { mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds); shouldShowBackgroundColor = false; } - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - if (shouldUseJumpCutForAnimation(animation)) { - return new ArrayList<>(); - } + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 3046307702c2..77799e99607b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -96,11 +96,9 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return customAnimation; - } + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; } // Use end bounds for opening. final Rect bounds = change.getEndAbsBounds(); @@ -130,11 +128,9 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return customAnimation; - } + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return customAnimation; } // Use start bounds for closing. final Rect bounds = change.getStartAbsBounds(); @@ -168,14 +164,12 @@ class ActivityEmbeddingAnimationSpec { @NonNull Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - // TODO(b/293658614): Support more complicated animations that may need more than a noop - // animation as the start leash. - final Animation noopAnimation = createNoopAnimation(change); - final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); - if (customAnimation != null) { - return new Animation[]{noopAnimation, customAnimation}; - } + // TODO(b/293658614): Support more complicated animations that may need more than a noop + // animation as the start leash. + final Animation noopAnimation = createNoopAnimation(change); + final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE); + if (customAnimation != null) { + return new Animation[]{noopAnimation, customAnimation}; } // Both start bounds and end bounds are in screen coordinates. We will post translate // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate @@ -320,13 +314,9 @@ class ActivityEmbeddingAnimationSpec { } final Animation anim; - if (Flags.activityEmbeddingAnimationCustomizationFlag()) { - // TODO(b/293658614): Consider allowing custom animations from non-default packages. - // Enforce limiting to animations from the default "android" package for now. - anim = mTransitionAnimation.loadDefaultAnimationRes(resId); - } else { - anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId); - } + // TODO(b/293658614): Consider allowing custom animations from non-default packages. + // Enforce limiting to animations from the default "android" package for now. + anim = mTransitionAnimation.loadDefaultAnimationRes(resId); if (anim != null) { return anim; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS new file mode 100644 index 000000000000..84596b015209 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/OWNERS @@ -0,0 +1,6 @@ +# WM shell sub-module automotive owners + +winsonc@google.com +stenning@google.com +gauravbhola@google.com +xiangw@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index c024840498ad..60a52a808a54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -214,6 +214,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Navigation window gone."); setTriggerBack(false); + // Trigger close transition if necessary. + if (Flags.migratePredictiveBackTransition()) { + mBackTransitionHandler.onAnimationFinished(); + } resetTouchTracker(); // Don't wait for animation start mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); @@ -731,6 +735,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont callback.onBackStarted(backEvent); if (mBackTransitionHandler.canHandOffAnimation()) { callback.setHandoffHandler(mHandoffHandler); + } else { + callback.setHandoffHandler(null); } mOnBackStartDispatched = true; } catch (RemoteException e) { @@ -1273,14 +1279,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @NonNull Transitions.TransitionFinishCallback finishCallback) { - final boolean isPrepareTransition = - info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; - if (isPrepareTransition) { - if (checkTakeoverFlags()) { - mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); - } - kickStartAnimation(); - } // Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't // need to post to ShellExecutor when called. if (info.getType() == WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION) { @@ -1308,7 +1306,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // animation never start, consume directly applyAndFinish(st, ft, finishCallback); return true; - } else if (mClosePrepareTransition == null && isPrepareTransition) { + } else if (mClosePrepareTransition == null + && info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) { // Gesture animation was cancelled before prepare transition ready, create // the close prepare transition createClosePrepareTransition(); @@ -1316,6 +1315,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (handlePrepareTransition(info, st, ft, finishCallback)) { + if (checkTakeoverFlags()) { + mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info); + } + kickStartAnimation(); return true; } return handleCloseTransition(info, st, ft, finishCallback); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 0fd4206c0545..de85d9af127d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -163,8 +163,11 @@ public class BubblePositioner { mExpandedViewLargeScreenWidth = (int) (bounds.width() * EXPANDED_VIEW_SMALL_TABLET_WIDTH_PERCENT); } else { - mExpandedViewLargeScreenWidth = - res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width); + int expandedViewLargeScreenSpacing = res.getDimensionPixelSize( + R.dimen.bubble_expanded_view_largescreen_landscape_padding); + mExpandedViewLargeScreenWidth = Math.min( + res.getDimensionPixelSize(R.dimen.bubble_expanded_view_largescreen_width), + bounds.width() - expandedViewLargeScreenSpacing * 2); } if (mDeviceConfig.isLargeScreen()) { if (mDeviceConfig.isSmallTablet()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 4d7c7fad53f8..3e8a9b64dac6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -272,6 +272,7 @@ public class BubbleBarAnimationHelper { final float startTx = getSwitchAnimationInitialTx(endTx); toBbev.setTranslationX(startTx); toBbev.getHandleView().setAlpha(0f); + toBbev.getHandleView().setHandleInitialColor(fromBbev.getHandleView().getHandleColor()); toBbev.animateExpansionWhenTaskViewVisible(() -> { AnimatorSet switchAnim = new AnimatorSet(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 2c0483c50710..65c929ab6fb4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -606,6 +606,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */); } + @VisibleForTesting + boolean isSurfaceZOrderedOnTop() { + return mTaskView != null && mTaskView.isZOrderedOnTop(); + } + /** * Sets whether the view is animating, in this case we won't change the content visibility * until the animation is done. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index 712e41b0b3c5..9cf0d2db710b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -65,6 +65,7 @@ public class BubbleBarHandleView extends View { @Nullable private ObjectAnimator mColorChangeAnim; private @ColorInt int mRegionSamplerColor; + private boolean mHasSampledColor; public BubbleBarHandleView(Context context) { this(context, null /* attrs */); @@ -102,7 +103,11 @@ public class BubbleBarHandleView extends View { invalidate(); } - private int getHandleColor() { + /** + * Get current color value for the handle + */ + @ColorInt + public int getHandleColor() { return mHandlePaint.getColor(); } @@ -128,6 +133,16 @@ public class BubbleBarHandleView extends View { } /** + * Set initial color for the handle. Takes effect if the + * {@link #updateHandleColor(boolean, boolean)} has not been called. + */ + public void setHandleInitialColor(@ColorInt int color) { + if (!mHasSampledColor) { + setHandleColor(color); + } + } + + /** * Updates the handle color. * * @param isRegionDark Whether the background behind the handle is dark, and thus the handle @@ -139,6 +154,7 @@ public class BubbleBarHandleView extends View { if (newColor == mRegionSamplerColor) { return; } + mHasSampledColor = true; mRegionSamplerColor = newColor; if (mColorChangeAnim != null) { mColorChangeAnim.cancel(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt new file mode 100644 index 000000000000..498d0e406e4b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/BoostExecutor.kt @@ -0,0 +1,47 @@ +/* + * 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.wm.shell.common + +import android.os.Looper +import java.util.concurrent.Executor + +/** Executor implementation which can be boosted temporarily to a different thread priority. */ +interface BoostExecutor : Executor { + /** + * Requests that the executor is boosted until {@link #resetBoost()} is called. + */ + fun setBoost() {} + + /** + * Requests that the executor is not boosted (only resets if there are no other boost requests + * in progress). + */ + fun resetBoost() {} + + /** + * Returns whether the executor is boosted. + */ + fun isBoosted() : Boolean { + return false + } + + /** + * Returns the looper for this executor. + */ + fun getLooper() : Looper? { + return Looper.myLooper() + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java index 736d954513b1..803f16ce39c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/HandlerExecutor.java @@ -16,15 +16,50 @@ package com.android.wm.shell.common; +import static android.os.Process.THREAD_PRIORITY_DEFAULT; +import static android.os.Process.setThreadPriority; + import android.annotation.NonNull; import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; + +import androidx.annotation.VisibleForTesting; + +import java.util.function.BiConsumer; /** Executor implementation which is backed by a Handler. */ public class HandlerExecutor implements ShellExecutor { + @NonNull private final Handler mHandler; + // See android.os.Process#THREAD_PRIORITY_* + private final int mDefaultThreadPriority; + private final int mBoostedThreadPriority; + // Number of current requests to boost thread priority + private int mBoostCount; + private final Object mBoostLock = new Object(); + // Default function for setting thread priority (tid, priority) + private BiConsumer<Integer, Integer> mSetThreadPriorityFn = + HandlerExecutor::setThreadPriorityInternal; public HandlerExecutor(@NonNull Handler handler) { + this(handler, THREAD_PRIORITY_DEFAULT, THREAD_PRIORITY_DEFAULT); + } + + /** + * Used only if this executor can be boosted, if so, it can be boosted to the given + * {@param boostPriority}. + */ + public HandlerExecutor(@NonNull Handler handler, int defaultThreadPriority, + int boostedThreadPriority) { mHandler = handler; + mDefaultThreadPriority = defaultThreadPriority; + mBoostedThreadPriority = boostedThreadPriority; + } + + @VisibleForTesting + void replaceSetThreadPriorityFn(BiConsumer<Integer, Integer> setThreadPriorityFn) { + mSetThreadPriorityFn = setThreadPriorityFn; } @Override @@ -56,9 +91,54 @@ public class HandlerExecutor implements ShellExecutor { } @Override + public void setBoost() { + synchronized (mBoostLock) { + if (mDefaultThreadPriority == mBoostedThreadPriority) { + // Nothing to boost + return; + } + if (mBoostCount == 0) { + mSetThreadPriorityFn.accept( + ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(), + mBoostedThreadPriority); + } + mBoostCount++; + } + } + + @Override + public void resetBoost() { + synchronized (mBoostLock) { + mBoostCount--; + if (mBoostCount == 0) { + mSetThreadPriorityFn.accept( + ((HandlerThread) mHandler.getLooper().getThread()).getThreadId(), + mDefaultThreadPriority); + } + } + } + + @Override + public boolean isBoosted() { + synchronized (mBoostLock) { + return mBoostCount > 0; + } + } + + @Override + @NonNull + public Looper getLooper() { + return mHandler.getLooper(); + } + + @Override public void assertCurrentThread() { if (!mHandler.getLooper().isCurrentThread()) { throw new IllegalStateException("must be called on " + mHandler); } } + + private static void setThreadPriorityInternal(Integer tid, Integer priority) { + setThreadPriority(tid, priority); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index 2c2961fd4b65..9e5071e8347b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -18,15 +18,15 @@ package com.android.wm.shell.common; import java.lang.reflect.Array; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; /** * Super basic Executor interface that adds support for delayed execution and removing callbacks. - * Intended to wrap Handler while better-supporting testing. + * Intended to wrap Handler while better-supporting testing. Not every ShellExecutor implementation + * may support boosting. */ -public interface ShellExecutor extends Executor { +public interface ShellExecutor extends BoostExecutor { /** * Executes the given runnable. If the caller is running on the same looper as this executor, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt index 819b11095a9b..2d0a3f54f85f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxCommandHandler.kt @@ -67,8 +67,8 @@ class LetterboxCommandHandler @Inject constructor( return false } return when (args.size) { - 1 -> onShellDisplayCommand(args[0], pw) - 2 -> onShellUpdateCommand(args[0], args[1], pw) + 1 -> onNoParamsCommand(args[0], pw) + 2 -> onSingleParamCommand(args[0], args[1], pw) else -> { pw.println("Invalid command: " + args[0]) return false @@ -89,11 +89,17 @@ class LetterboxCommandHandler @Inject constructor( $prefix name, for example, @android:color/system_accent2_50. $prefix backgroundColorReset" $prefix Resets the background color to the default value." + $prefix cornerRadius" + $prefix Corners radius (in pixels) for activities in the letterbox mode." + $prefix If cornerRadius < 0, it will be ignored and corners of the" + $prefix activity won't be rounded." + $prefix cornerRadiusReset" + $prefix Resets the rounded corners radius to the default value." """.trimIndent() ) } - private fun onShellUpdateCommand(command: String, value: String, pw: PrintWriter): Boolean { + private fun onSingleParamCommand(command: String, value: String, pw: PrintWriter): Boolean { when (command) { "backgroundColor" -> { return invokeWhenValid( @@ -120,10 +126,17 @@ class LetterboxCommandHandler @Inject constructor( } ) - "backgroundColorReset" -> { - letterboxConfiguration.resetLetterboxBackgroundColor() - return true - } + "cornerRadius" -> return invokeWhenValid( + pw, + value, + ::strToInt{ it >= 0 }, + { radius -> + letterboxConfiguration.setLetterboxActivityCornersRadius(radius) + }, + { r -> + "$r is not a valid radius. It must be an integer >= 0." + } + ) else -> { pw.println("Invalid command: $value") @@ -132,7 +145,7 @@ class LetterboxCommandHandler @Inject constructor( } } - private fun onShellDisplayCommand(command: String, pw: PrintWriter): Boolean { + private fun onNoParamsCommand(command: String, pw: PrintWriter): Boolean { when (command) { "backgroundColor" -> { pw.println( @@ -144,6 +157,24 @@ class LetterboxCommandHandler @Inject constructor( return true } + "backgroundColorReset" -> { + letterboxConfiguration.resetLetterboxBackgroundColor() + return true + } + + "cornerRadius" -> { + pw.println( + " Rounded corners radius: " + + "${letterboxConfiguration.getLetterboxActivityCornersRadius()} px." + ) + return true + } + + "cornerRadiusReset" -> { + letterboxConfiguration.resetLetterboxActivityCornersRadius() + return true + } + else -> { pw.println("Invalid command: $command") return false @@ -181,4 +212,15 @@ class LetterboxCommandHandler @Inject constructor( } catch (e: IllegalArgumentException) { null } + + // Converts a String to Int which if possible or it returns null otherwise. + // If a predicate is set, it also returns [null] if the predicate evaluate to [false]. + private fun strToInt(predicate: (Int) -> Boolean = { _ -> true }): (String) -> Int? = { str -> + try { + val value = str.toInt() + if (predicate(value)) value else null + } catch (e: IllegalArgumentException) { + null + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt index ef964f40dab3..8e149ac2869a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxUtils.kt @@ -66,3 +66,41 @@ infix fun LetterboxController.append(other: LetterboxController) = object : Lett other.dump() } } + +object LetterboxUtils { + // Utility methods about Maps usage in Letterbox. + object Maps { + /* + * Executes [onFound] on the [item] for a given [key] if present or + * [onMissed] if the [key] is not present. + */ + fun <V, K> MutableMap<K, V>.runOnItem( + key: K, + onFound: (V) -> Unit = { _ -> }, + onMissed: ( + K, + MutableMap<K, V> + ) -> Unit = { _, _ -> } + ) { + this[key]?.let { + return onFound(it) + } + return onMissed(key, this) + } + } + + // Utility methods about Transaction usage in Letterbox. + object Transactions { + // Sets position and crops in one method. + fun Transaction.moveAndCrop( + surface: SurfaceControl, + rect: Rect + ): Transaction = + setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) + .setWindowCrop( + surface, + rect.width(), + rect.height() + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt index 5129d03b9dbc..6861aa3763b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxController.kt @@ -20,6 +20,8 @@ import android.graphics.Rect import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Maps.runOnItem +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Transactions.moveAndCrop import com.android.wm.shell.dagger.WMSingleton import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT import javax.inject.Inject @@ -101,23 +103,6 @@ class MultiSurfaceLetterboxController @Inject constructor( ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}") } - /* - * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present. - */ - private fun MutableMap<LetterboxKey, LetterboxSurfaces>.runOnItem( - key: LetterboxKey, - onFound: (LetterboxSurfaces) -> Unit = { _ -> }, - onMissed: ( - LetterboxKey, - MutableMap<LetterboxKey, LetterboxSurfaces> - ) -> Unit = { _, _ -> } - ) { - this[key]?.let { - return onFound(it) - } - return onMissed(key, this) - } - private fun SurfaceControl?.remove( tx: Transaction ) = this?.let { @@ -131,17 +116,6 @@ class MultiSurfaceLetterboxController @Inject constructor( tx.setVisibility(this, visible) } - private fun Transaction.moveAndCrop( - surface: SurfaceControl, - rect: Rect - ): Transaction = - setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) - .setWindowCrop( - surface, - rect.width(), - rect.height() - ) - private fun LetterboxSurfaces.updateSurfacesBounds( tx: Transaction, taskBounds: Rect, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt index a67f6082c892..8e1cdee0c862 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt @@ -20,6 +20,8 @@ import android.graphics.Rect import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import com.android.internal.protolog.ProtoLog +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Maps.runOnItem +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Transactions.moveAndCrop import com.android.wm.shell.dagger.WMSingleton import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT import javax.inject.Inject @@ -106,32 +108,4 @@ class SingleSurfaceLetterboxController @Inject constructor( override fun dump() { ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}") } - - /* - * Executes [onFound] on the [SurfaceControl] if present or [onMissed] if not present. - */ - private fun MutableMap<LetterboxKey, SurfaceControl>.runOnItem( - key: LetterboxKey, - onFound: (SurfaceControl) -> Unit = { _ -> }, - onMissed: ( - LetterboxKey, - MutableMap<LetterboxKey, SurfaceControl> - ) -> Unit = { _, _ -> } - ) { - this[key]?.let { - return onFound(it) - } - return onMissed(key, this) - } - - private fun Transaction.moveAndCrop( - surface: SurfaceControl, - rect: Rect - ): Transaction = - setPosition(surface, rect.left.toFloat(), rect.top.toFloat()) - .setWindowCrop( - surface, - rect.width(), - rect.height() - ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt new file mode 100644 index 000000000000..d5e0240ea9ad --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/HasWMComponent.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.dagger + +/** + * An interface implemented by the application that uses [WMComponent]. + * + * This exposes the component to allow classes to do member injection for bindings where constructor + * injection is not possible, e.g. views. + */ +interface HasWMComponent { + fun getWMComponent(): WMComponent +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java index a3cdb2eff601..c493aadd57b0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java @@ -14,17 +14,14 @@ * limitations under the License. */ -package com.android.systemui.dagger; +package com.android.wm.shell.dagger; import android.os.HandlerThread; import androidx.annotation.Nullable; -import com.android.systemui.SystemUIInitializer; import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; -import com.android.wm.shell.dagger.WMShellModule; -import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.keyguard.KeyguardTransitions; @@ -45,12 +42,11 @@ import java.util.Optional; /** * Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported - * from the WM component into the SysUI component (in - * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies + * from the WM component into the SysUI component, and references the specific dependencies * provided by its particular device/form-factor SystemUI implementation. * - * ie. {@link WMComponent} includes {@link WMShellModule} - * and {@code TvWMComponent} includes {@link com.android.wm.shell.dagger.TvWMShellModule} + * <p> ie. {@link WMComponent} includes {@link WMShellModule} and {@code TvWMComponent} includes + * {@link TvWMShellModule} */ @WMSingleton @Subcomponent(modules = {WMShellModule.class}) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java index c5644a8f6876..d7ddbdeaa6da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.THREAD_PRIORITY_DISPLAY; +import static android.os.Process.THREAD_PRIORITY_FOREGROUND; import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST; import android.content.Context; @@ -205,13 +206,14 @@ public abstract class WMShellConcurrencyModule { } /** - * Provides a Shell background thread Executor for low priority background tasks. + * Provides a Shell background thread Executor for low priority background tasks. The thread + * may also be boosted to THREAD_PRIORITY_FOREGROUND if necessary. */ @WMSingleton @Provides @ShellBackgroundThread public static ShellExecutor provideSharedBackgroundExecutor( @ShellBackgroundThread Handler handler) { - return new HandlerExecutor(handler); + return new HandlerExecutor(handler, THREAD_PRIORITY_BACKGROUND, THREAD_PRIORITY_FOREGROUND); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 86e0d08ba05a..0cd0f4a97bbf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -152,6 +152,7 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController; @@ -347,7 +348,15 @@ public abstract class WMShellModule { @WMSingleton @Provides static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier( - @ShellMainThread @NonNull CoroutineScope mainScope) { + @NonNull Context context, + @ShellMainThread @NonNull CoroutineScope mainScope, + @NonNull ShellInit shellInit) { + final int poolSize = DesktopModeStatus.getWindowDecorScvhPoolSize(context); + final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize(); + if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) && poolSize > 0) { + return new PooledWindowDecorViewHostSupplier( + context, mainScope, shellInit, poolSize, preWarmSize); + } return new DefaultWindowDecorViewHostSupplier(mainScope); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 7764688695f7..50187d552b09 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -77,6 +77,10 @@ class DesktopMixedTransitionHandler( override fun startMinimizedModeTransition(wct: WindowContainerTransaction?): IBinder = freeformTaskTransitionHandler.startMinimizedModeTransition(wct) + /** Delegates starting PiP transition to [FreeformTaskTransitionHandler]. */ + override fun startPipTransition(wct: WindowContainerTransaction?): IBinder = + freeformTaskTransitionHandler.startPipTransition(wct) + /** Starts close transition and handles or delegates desktop task close animation. */ override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder { if ( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 80d8ecc127fe..cd37113666bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -53,6 +53,7 @@ import android.view.animation.DecelerateInterpolator; import androidx.annotation.VisibleForTesting; import com.android.internal.policy.SystemBarUtils; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; @@ -245,9 +246,17 @@ public class DesktopModeVisualIndicator { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final Resources resources = mContext.getResources(); final DisplayMetrics metrics = resources.getDisplayMetrics(); - final int screenWidth = metrics.widthPixels; - final int screenHeight = metrics.heightPixels; - + final int screenWidth; + final int screenHeight; + if (Flags.enableBugFixesForSecondaryDisplay()) { + final DisplayLayout displayLayout = + mDisplayController.getDisplayLayout(mTaskInfo.displayId); + screenWidth = displayLayout.width(); + screenHeight = displayLayout.height(); + } else { + screenWidth = metrics.widthPixels; + screenHeight = metrics.heightPixels; + } mView = new View(mContext); final SurfaceControl.Builder builder = new SurfaceControl.Builder(); mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index d5a2a4083b1d..0bc7ca982ec2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -50,6 +50,7 @@ import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_CLOSE import android.view.WindowManager.TRANSIT_NONE import android.view.WindowManager.TRANSIT_OPEN +import android.view.WindowManager.TRANSIT_PIP import android.view.WindowManager.TRANSIT_TO_FRONT import android.widget.Toast import android.window.DesktopModeFlags @@ -220,6 +221,7 @@ class DesktopTasksController( // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun. // Used to prevent handleRequest from moving the new fullscreen task to freeform. private var dragAndDropFullscreenCookie: Binder? = null + private var pendingPipTransitionAndTask: Pair<IBinder, Int>? = null init { desktopMode = DesktopModeImpl() @@ -361,8 +363,15 @@ class DesktopTasksController( } val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId) - requireNotNull(tdaInfo) { - "This method can only be called with the ID of a display having non-null DisplayArea." + // A non-organized display (e.g., non-trusted virtual displays used in CTS) doesn't have + // TDA. + if (tdaInfo == null) { + logW( + "forceEnterDesktop cannot find DisplayAreaInfo for displayId=%d. This could happen" + + " when the display is a non-trusted virtual display.", + displayId, + ) + return false } val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val isFreeformDisplay = tdaWindowingMode == WINDOWING_MODE_FREEFORM @@ -557,6 +566,26 @@ class DesktopTasksController( } fun minimizeTask(taskInfo: RunningTaskInfo) { + val wct = WindowContainerTransaction() + + val isMinimizingToPip = taskInfo.pictureInPictureParams?.isAutoEnterEnabled() ?: false + // If task is going to PiP, start a PiP transition instead of a minimize transition + if (isMinimizingToPip) { + val requestInfo = TransitionRequestInfo( + TRANSIT_PIP, /* triggerTask= */ null, taskInfo, /* remoteTransition= */ null, + /* displayChange= */ null, /* flags= */ 0 + ) + val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null) + wct.merge(requestRes.second, true) + pendingPipTransitionAndTask = + freeformTaskTransitionStarter.startPipTransition(wct) to taskInfo.taskId + return + } + + minimizeTaskInner(taskInfo) + } + + private fun minimizeTaskInner(taskInfo: RunningTaskInfo) { val taskId = taskInfo.taskId val displayId = taskInfo.displayId val wct = WindowContainerTransaction() @@ -884,7 +913,10 @@ class DesktopTasksController( destinationBounds.height(), displayController, ) - toggleResizeDesktopTaskTransitionHandler.startTransition(wct) + toggleResizeDesktopTaskTransitionHandler.startTransition( + wct, + interaction.animationStartBounds, + ) } private fun dragToMaximizeDesktopTask( @@ -915,6 +947,7 @@ class DesktopTasksController( direction = ToggleTaskSizeInteraction.Direction.MAXIMIZE, source = ToggleTaskSizeInteraction.Source.HEADER_DRAG_TO_TOP, inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent), + animationStartBounds = currentDragBounds, ), ) } @@ -1202,9 +1235,12 @@ class DesktopTasksController( moveHomeTask(wct, toTop = true) // Currently, we only handle the desktop on the default display really. - if (displayId == DEFAULT_DISPLAY && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) { + if ( + (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) && + ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue() + ) { // Add translucent wallpaper activity to show the wallpaper underneath - addWallpaperActivity(wct) + addWallpaperActivity(displayId, wct) } val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId) @@ -1253,7 +1289,7 @@ class DesktopTasksController( ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) } } - private fun addWallpaperActivity(wct: WindowContainerTransaction) { + private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) { logV("addWallpaperActivity") val userHandle = UserHandle.of(userId) val userContext = context.createContextAsUser(userHandle, /* flags= */ 0) @@ -1264,6 +1300,9 @@ class DesktopTasksController( launchWindowingMode = WINDOWING_MODE_FULLSCREEN pendingIntentBackgroundActivityStartMode = ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS + if (Flags.enableBugFixesForSecondaryDisplay()) { + launchDisplayId = displayId + } } val pendingIntent = PendingIntent.getActivityAsUser( @@ -1329,6 +1368,21 @@ class DesktopTasksController( return false } + override fun onTransitionConsumed( + transition: IBinder, + aborted: Boolean, + finishT: Transaction? + ) { + pendingPipTransitionAndTask?.let { (pipTransition, taskId) -> + if (transition == pipTransition) { + if (aborted) { + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { minimizeTaskInner(it) } + } + pendingPipTransitionAndTask = null + } + } + } + override fun handleRequest( transition: IBinder, request: TransitionRequestInfo, @@ -2048,7 +2102,11 @@ class DesktopTasksController( syncQueue, taskInfo, displayController, - context, + if (Flags.enableBugFixesForSecondaryDisplay()) { + displayController.getDisplayContext(taskInfo.displayId) + } else { + context + }, taskSurface, rootTaskDisplayAreaOrganizer, dragStartState, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt index 7afd8d7f6e48..f6ebf7221e82 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/ToggleTaskSizeUtils.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.desktopmode.common +import android.graphics.Rect import com.android.internal.jank.Cuj import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger @@ -23,10 +24,13 @@ import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Ambiguo import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction.Source /** Represents a user interaction to toggle a desktop task's size from to maximize or vice versa. */ -data class ToggleTaskSizeInteraction( +data class ToggleTaskSizeInteraction +@JvmOverloads +constructor( val direction: Direction, val source: Source, val inputMethod: InputMethod, + val animationStartBounds: Rect? = null, ) { constructor( isMaximized: Boolean, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md index 9d015357b60b..837a6dd32ff2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md @@ -36,7 +36,8 @@ the product. thread) - This is always another thread even if config_enableShellMainThread is not set true - **Note**: - - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority + - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority but can be requested to be boosted + to `THREAD_PRIORITY_FOREGROUND` - `ShellAnimationThread` (currently only used for Transitions and Splitscreen, but potentially all animations could be offloaded here) - `ShellSplashScreenThread` (only for use with splashscreens) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 2ae9828ca0db..52b6c62b0721 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -18,6 +18,7 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_PIP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -99,6 +100,12 @@ public class FreeformTaskTransitionHandler return token; } + @Override + public IBinder startPipTransition(WindowContainerTransaction wct) { + final IBinder token = mTransitions.startTransition(TRANSIT_PIP, wct, null); + mPendingTransitionTokens.add(token); + return token; + } @Override public IBinder startRemoveTransition(WindowContainerTransaction wct) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java index 5984d486f838..a874a5be426d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionStarter.java @@ -51,4 +51,13 @@ public interface FreeformTaskTransitionStarter { * @return the started transition */ IBinder startRemoveTransition(WindowContainerTransaction wct); + + /** + * Starts PiP transition + * + * @param wct the {@link WindowContainerTransaction} that launches the PiP + * + * @return the started transition + */ + IBinder startPipTransition(WindowContainerTransaction wct); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 44cc563eadf4..fc3fbe299605 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -222,7 +222,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger, menuController, mainExecutor, mPipPerfHintController); - mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); + mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> { + updateMinMaxSize(aspectRatio); + onAspectRatioChanged(); + }); mMoveOnShelVisibilityChanged = () -> { if (mIsImeShowing && mImeHeight > mShelfHeight) { @@ -768,16 +771,17 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha private void animateToNormalSize(Runnable callback) { // Save the current bounds as the user-resize bounds. mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds()); + final Rect adjustedNormalBounds = getAdjustedNormalBounds(); + mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds, + getMovementBounds(mPipBoundsState.getBounds()), + getMovementBounds(adjustedNormalBounds), callback /* callback */); + } + private Rect getAdjustedNormalBounds() { final Size minMenuSize = mMenuController.getEstimatedMinMenuSize(); final Size defaultSize = mSizeSpecSource.getDefaultSize(mPipBoundsState.getAspectRatio()); final Rect normalBounds = new Rect(0, 0, defaultSize.getWidth(), defaultSize.getHeight()); - final Rect adjustedNormalBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu( - normalBounds, minMenuSize); - - mSavedSnapFraction = mMotionHelper.animateToExpandedState(adjustedNormalBounds, - getMovementBounds(mPipBoundsState.getBounds()), - getMovementBounds(adjustedNormalBounds), callback /* callback */); + return mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize); } private void animateToUnexpandedState(Rect restoreBounds) { @@ -1065,6 +1069,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(), insetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0); mMotionHelper.onMovementBoundsChanged(); + + if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) { + mPipResizeGestureHandler.setUserResizeBounds(getAdjustedNormalBounds()); + } } private Rect getMovementBounds(Rect curBounds) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 1a012e075be5..dae3c21b6697 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -55,6 +55,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; +import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -196,7 +197,7 @@ public class PipTransition extends PipTransitionController implements @NonNull TransitionRequestInfo request) { if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { mEnterTransition = transition; - return getEnterPipTransaction(transition, request); + return getEnterPipTransaction(transition, request.getPipChange()); } return null; } @@ -205,7 +206,8 @@ public class PipTransition extends PipTransitionController implements public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWct) { if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) { - outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */); + outWct.merge(getEnterPipTransaction(transition, request.getPipChange()), + true /* transfer */); mEnterTransition = transition; } } @@ -728,6 +730,10 @@ public class PipTransition extends PipTransitionController implements && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) { adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top); } + if (Flags.enableDesktopWindowingPip()) { + adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left, + -pipActivityChange.getStartAbsBounds().top); + } } else { // For non-valid app provided src-rect-hint, calculate one to crop into during // app icon overlay animation. @@ -775,9 +781,9 @@ public class PipTransition extends PipTransitionController implements } private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition, - @NonNull TransitionRequestInfo request) { + @NonNull TransitionRequestInfo.PipChange pipChange) { // cache the original task token to check for multi-activity case later - final ActivityManager.RunningTaskInfo pipTask = request.getPipTask(); + final ActivityManager.RunningTaskInfo pipTask = pipChange.getTaskInfo(); PictureInPictureParams pipParams = pipTask.pictureInPictureParams; mPipTaskListener.setPictureInPictureParams(pipParams); mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo, @@ -787,14 +793,18 @@ public class PipTransition extends PipTransitionController implements final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); mPipBoundsState.setBounds(entryBounds); + // Operate on the TF token in case we are dealing with AE case; this should avoid marking + // activities in other TFs as config-at-end. + WindowContainerToken token = pipChange.getTaskFragmentToken(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds); - wct.deferConfigToTransitionEnd(pipTask.token); + wct.movePipActivityToPinnedRootTask(token, entryBounds); + wct.deferConfigToTransitionEnd(token); return wct; } private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) { - final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask(); + final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null + ? requestInfo.getPipChange().getTaskInfo() : null; if (pipTask == null) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index b922cd029468..0869caa55369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -550,7 +550,9 @@ public class RecentTasksController implements TaskStackListenerCallback, groupedTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - if (Flags.enableRefactorTaskThumbnail() && isWallpaperTask(taskInfo)) { + if ( + Flags.enableUseTopVisibleActivityForExcludeFromRecentTask() + && isWallpaperTask(taskInfo)) { // Don't add the wallpaper task as an entry in grouped tasks continue; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 37d58780fc85..032dac9ff3a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -1317,6 +1317,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler, // otherwise a new transition will notify the relevant observers if (returningToApp && allAppsAreTranslucent(mPausingTasks)) { mHomeTransitionObserver.notifyHomeVisibilityChanged(true); + } else if (!toHome && mState == STATE_NEW_TASK + && allAppsAreTranslucent(mOpeningTasks)) { + // We are opening a translucent app. Launcher is still visible so we do nothing. } else if (!toHome) { // For some transitions, we may have notified home activity that it became // visible. We need to notify the observer that we are no longer going home. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java index 82c0aaf3bc8b..361d766370e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskView.java @@ -186,6 +186,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback, */ public void setObscuredTouchRect(Rect obscuredRect) { mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null; + invalidate(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 9dbac761d104..0f5813c7807b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -588,8 +588,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private void openHandleMenu(int taskId) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); - decoration.createHandleMenu(checkNumberOfOtherInstances(decoration.mTaskInfo) - >= MANAGE_WINDOWS_MINIMUM_INSTANCES); + // TODO(b/379873022): Run the instance check and the AssistContent request in + // createHandleMenu on the same bg thread dispatch. + mBgExecutor.execute(() -> { + final int numOfInstances = checkNumberOfOtherInstances(decoration.mTaskInfo); + mMainExecutor.execute(() -> { + decoration.createHandleMenu( + numOfInstances >= MANAGE_WINDOWS_MINIMUM_INSTANCES + ); + }); + }); } private void onToggleSizeInteraction( @@ -763,12 +771,20 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, return; } decoration.closeHandleMenu(); - decoration.createManageWindowsMenu(getTaskSnapshots(decoration.mTaskInfo), - /* onIconClickListener= */(Integer requestedTaskId) -> { - decoration.closeManageWindowsMenu(); - mDesktopTasksController.openInstance(decoration.mTaskInfo, requestedTaskId); - return Unit.INSTANCE; - }); + mBgExecutor.execute(() -> { + final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList = + getTaskSnapshots(decoration.mTaskInfo); + mMainExecutor.execute(() -> decoration.createManageWindowsMenu( + snapshotList, + /* onIconClickListener= */ (Integer requestedTaskId) -> { + decoration.closeManageWindowsMenu(); + mDesktopTasksController.openInstance(decoration.mTaskInfo, + requestedTaskId); + return Unit.INSTANCE; + } + ) + ); + }); } private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots( @@ -1618,7 +1634,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, } final DesktopModeWindowDecoration windowDecoration = mDesktopModeWindowDecorFactory.create( - mContext, + Flags.enableBugFixesForSecondaryDisplay() + ? mDisplayController.getDisplayContext(taskInfo.displayId) + : mContext, mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */), mDisplayController, mSplitScreenController, @@ -1814,11 +1832,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, // TODO(b/336289597): Rather than returning number of instances, return a list of valid // instances, then refer to the list's size and reuse the list for Manage Windows menu. final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService(); - final IActivityManager activityManager = ActivityManager.getService(); try { return activityTaskManager.getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_WITH_EXCLUDED, - activityManager.getCurrentUserId()).getList().stream().filter( + info.userId).getList().stream().filter( recentTaskInfo -> (recentTaskInfo.taskId != info.taskId && recentTaskInfo.baseActivity != null && recentTaskInfo.baseActivity.getPackageName() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..47cfaeed6157 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt @@ -0,0 +1,102 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.os.Trace +import android.util.Pools +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import com.android.wm.shell.sysui.ShellInit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** + * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be + * expensive to recreate for each new or updated window decoration. + * + * Callers can obtain a [WindowDecorViewHost] using [acquire], which will return a pooled object if + * available, or create a new instance and return it if needed. When finished using a + * [WindowDecorViewHost], it must be released using [release] to allow it to be sent back into the + * pool and reused later on. + * + * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put + * into the pool immediately after creation. + */ +class PooledWindowDecorViewHostSupplier( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + shellInit: ShellInit, + maxPoolSize: Int, + private val preWarmSize: Int, +) : WindowDecorViewHostSupplier<WindowDecorViewHost> { + + private val pool: Pools.Pool<WindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize) + private var nextDecorViewHostId = 0 + + init { + require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" } + shellInit.addInitCallback(this::onShellInit, this) + } + + private fun onShellInit() { + if (preWarmSize <= 0) { + return + } + preWarmViewHosts(preWarmSize) + } + + private fun preWarmViewHosts(preWarmSize: Int) { + mainScope.launch { + // Applying isn't needed, as the surface was never actually shown. + val t = SurfaceControl.Transaction() + repeat(preWarmSize) { + val warmedViewHost = newInstance(context, context.display).apply { warmUp() } + // Put the warmed view host in the pool by releasing it. + release(warmedViewHost, t) + } + } + } + + override fun acquire(context: Context, display: Display): WindowDecorViewHost { + val pooledViewHost = pool.acquire() + if (pooledViewHost != null) { + return pooledViewHost + } + Trace.beginSection("PooledWindowDecorViewHostSupplier#acquire-newInstance") + val newDecorViewHost = newInstance(context, display) + Trace.endSection() + return newDecorViewHost + } + + override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) { + val pooled = pool.release(viewHost) + if (!pooled) { + viewHost.release(t) + } + } + + private fun newInstance(context: Context, display: Display): ReusableWindowDecorViewHost { + // Use a reusable window decor view host, as it allows swapping the entire view hierarchy. + return ReusableWindowDecorViewHost( + context = context, + mainScope = mainScope, + display = display, + id = nextDecorViewHostId++, + ) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt new file mode 100644 index 000000000000..da41e1b1d8d8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt @@ -0,0 +1,149 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.graphics.PixelFormat +import android.graphics.Region +import android.view.Display +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE +import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH +import android.view.WindowManager.LayoutParams.TYPE_APPLICATION +import android.widget.FrameLayout +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * An implementation of [WindowDecorViewHost] that supports: + * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be called with + * different [View] instances. This is useful when reusing [WindowDecorViewHost]s instances for + * vastly different view hierarchies, such as Desktop Windowing's App Handles and App Headers. + * 2) Pre-warming of the underlying [SurfaceControlViewHostAdapter]s. Useful because their creation + * and first root view assignment are expensive, which is undesirable in latency-sensitive code + * paths like during a shell transition. + */ +class ReusableWindowDecorViewHost( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + display: Display, + val id: Int, + @VisibleForTesting + val viewHostAdapter: SurfaceControlViewHostAdapter = + SurfaceControlViewHostAdapter(context, display), +) : WindowDecorViewHost, Warmable { + @VisibleForTesting val rootView = FrameLayout(context) + + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = viewHostAdapter.rootSurface + + override fun warmUp() { + if (viewHostAdapter.isInitialized()) { + // Already warmed up. + return + } + Trace.beginSection("$TAG#warmUp") + viewHostAdapter.prepareViewHost(context.resources.configuration, touchableRegion = null) + viewHostAdapter.updateView( + rootView, + WindowManager.LayoutParams( + 0 /* width*/, + 0 /* height */, + TYPE_APPLICATION, + FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH, + PixelFormat.TRANSPARENT, + ) + .apply { + setTitle("View root of $TAG#$id") + setTrustedOverlay() + }, + ) + Trace.endSection() + } + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = + mainScope.launch { + updateViewHost( + view, + attrs, + configuration, + touchableRegion, + onDrawTransaction = null, + ) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHostAdapter.release(t) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) { + Trace.beginSection("ReusableWindowDecorViewHost#updateViewHost") + viewHostAdapter.prepareViewHost(configuration, touchableRegion) + onDrawTransaction?.let { viewHostAdapter.applyTransactionOnDraw(it) } + rootView.removeAllViews() + rootView.addView(view) + viewHostAdapter.updateView(rootView, attrs) + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } + + companion object { + private const val TAG = "ReusableWindowDecorViewHost" + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/Warmable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/Warmable.kt new file mode 100644 index 000000000000..2cb0f891436b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/Warmable.kt @@ -0,0 +1,23 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +/** + * An interface for an object that can be warmed up before it's needed. + */ +interface Warmable { + fun warmUp() +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index f22e2a591df3..a2afd2c92d3d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -33,6 +33,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -634,7 +636,7 @@ public class BackAnimationControllerTest extends ShellTestCase { releaseBackGesture(); mShellExecutor.flushAll(); - verify(mAppCallback).setHandoffHandler(any()); + verify(mAppCallback).setHandoffHandler(notNull()); } @Test @@ -654,7 +656,7 @@ public class BackAnimationControllerTest extends ShellTestCase { releaseBackGesture(); mShellExecutor.flushAll(); - verify(mAppCallback, never()).setHandoffHandler(any()); + verify(mAppCallback).setHandoffHandler(isNull()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java index 329a10998f23..bf03834c70d8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java @@ -15,9 +15,12 @@ */ package com.android.wm.shell.bubbles.bar; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import android.graphics.Color; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -61,4 +64,18 @@ public class BubbleBarHandleViewTest extends ShellTestCase { assertEquals(handleColor, ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); } + + @Test + public void testSetHandleInitialColor_beforeUpdateHandleColor_updatesColor() { + mHandleView.setHandleInitialColor(Color.RED); + assertThat(mHandleView.getHandleColor()).isEqualTo(Color.RED); + } + + @Test + public void testSetHandleInitialColor_afterUpdateHandleColor_doesNotUpdateColor() { + mHandleView.updateHandleColor(/* isRegionDark= */ true, /* animated= */ false); + mHandleView.setHandleInitialColor(Color.RED); + assertThat(mHandleView.getHandleColor()).isEqualTo( + ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light)); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt new file mode 100644 index 000000000000..799b48c2504f --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt @@ -0,0 +1,173 @@ +/* + * 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.wm.shell.common + +import android.os.Handler +import android.os.HandlerThread +import android.os.Looper +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import java.util.function.BiConsumer +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.MockitoSession +import org.mockito.kotlin.whenever + +/** + * Tests for HandlerExecutor. + * + * Build/Install/Run: + * atest WMShellUnitTests:HandlerExecutorTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class HandlerExecutorTest : ShellTestCase() { + + class TestSetThreadPriorityFn : BiConsumer<Int, Int> { + var lastSetPriority = UNSET_THREAD_PRIORITY + private set + var callCount = 0 + private set + + override fun accept(tid: Int, priority: Int) { + lastSetPriority = priority + callCount++ + } + + fun reset() { + lastSetPriority = UNSET_THREAD_PRIORITY + callCount = 0 + } + } + + val testSetPriorityFn = TestSetThreadPriorityFn() + + @Test + fun defaultExecutorDisallowBoost() { + val executor = createTestHandlerExecutor() + + executor.setBoost() + + assertThat(executor.isBoosted()).isFalse() + } + + @Test + fun boostExecutor_resetWhenNotSet_expectNoOp() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Try to reset and ensure we never try to set the thread priority + executor.resetBoost() + + assertThat(testSetPriorityFn.callCount).isEqualTo(0) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + @Test + fun boostExecutor_setResetBoost_expectThreadPriorityUpdated() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Boost and ensure the boosted thread priority is requested + executor.setBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset and ensure the default thread priority is requested + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(2) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + @Test + fun boostExecutor_overlappingBoost_expectResetOnlyWhenNotOverlapping() { + val executor = createTestHandlerExecutor(DEFAULT_THREAD_PRIORITY, BOOSTED_THREAD_PRIORITY) + val mockSession: MockitoSession = ExtendedMockito.mockitoSession() + .mockStatic(android.os.Process::class.java) + .startMocking() + + try { + // Set and ensure we only update the thread priority once + executor.setBoost() + executor.setBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset and ensure we are still boosted and the thread priority doesn't change + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(BOOSTED_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(1) + assertThat(executor.isBoosted()).isTrue() + + // Reset again and ensure we update the thread priority accordingly + executor.resetBoost() + + assertThat(testSetPriorityFn.lastSetPriority).isEqualTo(DEFAULT_THREAD_PRIORITY) + assertThat(testSetPriorityFn.callCount).isEqualTo(2) + assertThat(executor.isBoosted()).isFalse() + } finally { + mockSession.finishMocking() + } + } + + /** + * Creates a test handler executor backed by a mocked handler thread. + */ + private fun createTestHandlerExecutor( + defaultThreadPriority: Int = DEFAULT_THREAD_PRIORITY, + boostedThreadPriority: Int = DEFAULT_THREAD_PRIORITY + ) : HandlerExecutor { + val handler = mock(Handler::class.java) + val looper = mock(Looper::class.java) + val thread = mock(HandlerThread::class.java) + whenever(handler.looper).thenReturn(looper) + whenever(looper.thread).thenReturn(thread) + whenever(thread.threadId).thenReturn(1234) + val executor = HandlerExecutor(handler, defaultThreadPriority, boostedThreadPriority) + executor.replaceSetThreadPriorityFn(testSetPriorityFn) + return executor + } + + companion object { + private const val UNSET_THREAD_PRIORITY = 0 + private const val DEFAULT_THREAD_PRIORITY = 1 + private const val BOOSTED_THREAD_PRIORITY = 1000 + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java index 94dbd112bb75..4c97c76ae122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java @@ -19,6 +19,7 @@ package com.android.wm.shell.compatui; import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.window.flags.Flags.FLAG_APP_COMPAT_ASYNC_RELAYOUT; import static com.android.window.flags.Flags.FLAG_APP_COMPAT_UI_FRAMEWORK; import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_HIDDEN; import static com.android.wm.shell.compatui.CompatUIStatusManager.COMPAT_UI_EDUCATION_VISIBLE; @@ -42,10 +43,12 @@ import android.app.ActivityManager; import android.app.TaskInfo; import android.graphics.Insets; import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.DisplayCutout; @@ -125,6 +128,9 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Mock private Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback; @Mock private DockStateReader mDockStateReader; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private CompatUIConfiguration mCompatUIConfiguration; private TestShellExecutor mExecutor; private FakeCompatUIStatusManagerTest mCompatUIStatus; @@ -317,6 +323,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Test @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) + @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT) public void testUpdateCompatInfo_updatesLayoutCorrectly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -346,6 +353,36 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Test @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) + @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT) + public void testUpdateCompatInfo_updatesLayoutCorrectlyAsync() { + LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); + + assertTrue(windowManager.createLayout(/* canShow= */ true)); + LetterboxEduDialogLayout layout = windowManager.mLayout; + assertNotNull(layout); + + assertTrue(windowManager.updateCompatInfo( + createTaskInfo(/* eligible= */ true, USER_ID_1, new Rect(50, 25, 150, 75)), + mTaskListener, /* canShow= */ true)); + + verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ 100, + /* expectedHeight= */ 50, /* expectedExtraTopMargin= */ 0, + /* expectedExtraBottomMargin= */ 0); + verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any()); + assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams()); + + // Window manager should be released (without animation) when eligible becomes false. + assertFalse(windowManager.updateCompatInfo(createTaskInfo(/* eligible= */ false), + mTaskListener, /* canShow= */ true)); + + verify(windowManager).release(); + verify(mOnDismissCallback, never()).accept(any()); + verify(mAnimationController, never()).startExitAnimation(any(), any()); + assertNull(windowManager.mLayout); + } + + @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testUpdateCompatInfo_notEligibleUntilUpdate_createsLayoutAfterUpdate() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ false); @@ -375,6 +412,7 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Test @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) + @DisableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT) public void testUpdateDisplayLayout_updatesLayoutCorrectly() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); @@ -397,6 +435,29 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { @Test @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) + @EnableFlags(FLAG_APP_COMPAT_ASYNC_RELAYOUT) + public void testUpdateDisplayLayout_updatesLayoutCorrectlyAsync() { + LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); + + assertTrue(windowManager.createLayout(/* canShow= */ true)); + LetterboxEduDialogLayout layout = windowManager.mLayout; + assertNotNull(layout); + + int newDisplayCutoutTop = DISPLAY_CUTOUT_TOP + 7; + int newDisplayCutoutBottom = DISPLAY_CUTOUT_BOTTOM + 9; + windowManager.updateDisplayLayout(createDisplayLayout( + Insets.of(DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutTop, + DISPLAY_CUTOUT_HORIZONTAL, newDisplayCutoutBottom))); + + verifyLayout(layout, layout.getLayoutParams(), /* expectedWidth= */ TASK_WIDTH, + /* expectedHeight= */ TASK_HEIGHT, /* expectedExtraTopMargin= */ + newDisplayCutoutTop, /* expectedExtraBottomMargin= */ newDisplayCutoutBottom); + verify(mViewHost).relayout(mWindowAttrsCaptor.capture(), any()); + assertThat(mWindowAttrsCaptor.getValue()).isEqualTo(layout.getLayoutParams()); + } + + @Test + @RequiresFlagsDisabled(FLAG_APP_COMPAT_UI_FRAMEWORK) public void testRelease_animationIsCancelled() { LetterboxEduWindowManager windowManager = createWindowManager(/* eligible= */ true); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt index 06b805233ee7..667511288bfa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt @@ -19,9 +19,14 @@ package com.android.wm.shell.compatui.letterbox import android.content.Context import android.graphics.Rect import android.testing.AndroidTestingRunner +import android.view.SurfaceControl import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Maps.runOnItem +import com.android.wm.shell.compatui.letterbox.LetterboxUtils.Transactions.moveAndCrop import java.util.function.Consumer +import kotlin.test.assertEquals +import kotlin.test.assertNull import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any @@ -100,6 +105,35 @@ class LetterboxUtilsTest : ShellTestCase() { } } + @Test + fun `runOnItem executes onFound when an item has been found for a key`() { + runTestScenario { r -> + r.initMap(1 to 2, 3 to 4) + r.runOnItem<Int>(1) + r.verifyOnItemInvoked(expectedItem = 2) + r.verifyOnMissingNotInvoked() + } + } + + @Test + fun `runOnItem executes onMissing when an item has not been found for a key`() { + runTestScenario { r -> + r.initMap(1 to 2, 3 to 4) + r.runOnItem<Int>(8) + r.verifyOnItemNotInvoked() + r.verifyOnMissingInvoked(expectedKey = 8) + } + } + + @Test + fun `moveAndCrop invoked Move and then Crop`() { + runTestScenario { r -> + r.invoke(Rect(1, 2, 51, 62)) + r.verifySetPosition(expectedX = 1f, expectedY = 2f) + r.verifySetWindowCrop(expectedWidth = 50, expectedHeight = 60) + } + } + /** * Runs a test scenario providing a Robot. */ @@ -113,6 +147,14 @@ class LetterboxUtilsTest : ShellTestCase() { builder: (LetterboxSurfaceBuilder) -> LetterboxController ) : LetterboxControllerRobotTest(ctx, builder) { + private var testableMap = mutableMapOf<Int, Int>() + private var onItemState: Int? = null + private var onMissingStateKey: Int? = null + private var onMissingStateMap: MutableMap<Int, Int>? = null + + private val transaction = getTransactionMock() + private val surface = SurfaceControl() + fun verifyCreateSurfaceInvokedWithRequest( target: LetterboxController, times: Int = 1 @@ -147,5 +189,46 @@ class LetterboxUtilsTest : ShellTestCase() { ) { verify(target, times(times)).dump() } + + fun initMap(vararg values: Pair<Int, Int>) = testableMap.putAll(values.toMap()) + + fun <T> runOnItem(key: Int) { + testableMap.runOnItem(key, onFound = { item -> + onItemState = item + }, onMissed = { k, m -> + onMissingStateKey = k + onMissingStateMap = m + }) + } + + fun verifyOnItemInvoked(expectedItem: Int) { + assertEquals(expectedItem, onItemState) + } + + fun verifyOnItemNotInvoked() { + assertNull(onItemState) + } + + fun verifyOnMissingInvoked(expectedKey: Int) { + assertEquals(expectedKey, onMissingStateKey) + assertEquals(onMissingStateMap, testableMap) + } + + fun verifyOnMissingNotInvoked() { + assertNull(onMissingStateKey) + assertNull(onMissingStateMap) + } + + fun invoke(rect: Rect) { + transaction.moveAndCrop(surface, rect) + } + + fun verifySetPosition(expectedX: Float, expectedY: Float) { + verify(transaction).setPosition(surface, expectedX, expectedY) + } + + fun verifySetWindowCrop(expectedWidth: Int, expectedHeight: Int) { + verify(transaction).setWindowCrop(surface, expectedWidth, expectedHeight) + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 3bee588feee9..7c9494ce7026 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -21,6 +21,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.KeyguardManager import android.app.PendingIntent +import android.app.PictureInPictureParams import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -1724,6 +1725,34 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun onDesktopWindowMinimize_pipTask_autoEnterEnabled_startPipTransition() { + val task = setUpPipTask(autoEnterEnabled = true) + val handler = mock(TransitionHandler::class.java) + whenever(freeformTaskTransitionStarter.startPipTransition(any())) + .thenReturn(Binder()) + whenever(transitions.dispatchRequest(any(), any(), anyOrNull())) + .thenReturn(android.util.Pair(handler, WindowContainerTransaction()) + ) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startPipTransition(any()) + verify(freeformTaskTransitionStarter, never()).startMinimizedModeTransition(any()) + } + + @Test + fun onDesktopWindowMinimize_pipTask_autoEnterDisabled_startMinimizeTransition() { + val task = setUpPipTask(autoEnterEnabled = false) + whenever(freeformTaskTransitionStarter.startMinimizedModeTransition(any())) + .thenReturn(Binder()) + + controller.minimizeTask(task) + + verify(freeformTaskTransitionStarter).startMinimizedModeTransition(any()) + verify(freeformTaskTransitionStarter, never()).startPipTransition(any()) + } + + @Test fun onDesktopWindowMinimize_singleActiveTask_noWallpaperActivityToken_doesntRemoveWallpaper() { val task = setUpFreeformTask(active = true) val transition = Binder() @@ -3033,20 +3062,21 @@ class DesktopTasksControllerTest : ShellTestCase() { .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR) // Drag move the task to the top edge + val currentDragBounds = Rect(100, 50, 500, 1000) spyController.onDragPositioningMove(task, mockSurface, 200f, Rect(100, 200, 500, 1000)) spyController.onDragPositioningEnd( task, mockSurface, Point(100, 50), /* position */ PointF(200f, 300f), /* inputCoordinate */ - Rect(100, 50, 500, 1000), /* currentDragBounds */ + currentDragBounds, Rect(0, 50, 2000, 2000) /* validDragArea */, Rect() /* dragStartBounds */, motionEvent, desktopWindowDecoration) // Assert bounds set to stable bounds - val wct = getLatestToggleResizeDesktopTaskWct() + val wct = getLatestToggleResizeDesktopTaskWct(currentDragBounds) assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) // Assert event is properly logged verify(desktopModeEventLogger, times(1)).logTaskResizingStarted( @@ -4228,6 +4258,14 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } + private fun setUpPipTask(autoEnterEnabled: Boolean): RunningTaskInfo { + return setUpFreeformTask().apply { + pictureInPictureParams = PictureInPictureParams.Builder() + .setAutoEnterEnabled(autoEnterEnabled) + .build() + } + } + private fun setUpHomeTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createHomeTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index c6835b7bde55..22b45e8c63af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.launcher3.Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL; +import static com.android.launcher3.Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50; @@ -250,7 +250,7 @@ public class RecentTasksControllerTest extends ShellTestCase { t3.taskId, -1); } - @EnableFlags(FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @EnableFlags(FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) @Test public void testGetRecentTasks_removesDesktopWallpaperActivity() { RecentTaskInfo t1 = makeTaskInfo(1); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt index a15b61122713..8b4cf6d1fabe 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt @@ -125,6 +125,8 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest : times(1) ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId) + bgExecutor.flushAll() + testShellExecutor.flushAll() verify(decor, times(1)).createHandleMenu(anyBoolean()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 117c85e322ad..88f62d10913d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -780,6 +780,8 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest times(1) ).setAppHandleEducationTooltipCallbacks(openHandleMenuCallbackCaptor.capture(), any()) openHandleMenuCallbackCaptor.lastValue.invoke(task.taskId) + bgExecutor.flushAll() + testShellExecutor.flushAll() verify(decor, times(1)).createHandleMenu(anyBoolean()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index dce44b7c829f..6be234ef5ca6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -286,6 +286,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { } else { statusBars() } + userId = context.userId } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt new file mode 100644 index 000000000000..92f5def508c7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt @@ -0,0 +1,160 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.content.res.Configuration +import android.graphics.Region +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.util.StubTransaction +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock + +/** + * Tests for [PooledWindowDecorViewHostSupplier]. + * + * Build/Install/Run: atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest + */ +@SmallTest +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +class PooledWindowDecorViewHostSupplierTest : ShellTestCase() { + + private val testExecutor = TestShellExecutor() + private val testShellInit = ShellInit(testExecutor) + + private lateinit var supplier: PooledWindowDecorViewHostSupplier + + @Test + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun onInit_warmsAndPoolsViewHosts() = runTest { + supplier = createSupplier(maxPoolSize = 5, preWarmSize = 2) + + testExecutor.flushAll() + advanceUntilIdle() + + val viewHost1 = supplier.acquire(context, context.display) as ReusableWindowDecorViewHost + val viewHost2 = supplier.acquire(context, context.display) as ReusableWindowDecorViewHost + + // Acquired warmed up view hosts from the pool. + assertThat(viewHost1.viewHostAdapter.isInitialized()).isTrue() + assertThat(viewHost2.viewHostAdapter.isInitialized()).isTrue() + } + + @Test(expected = Throwable::class) + fun onInit_warmUpSizeExceedsPoolSize_throws() = runTest { + createSupplier(maxPoolSize = 3, preWarmSize = 4) + } + + @Test + fun acquire_poolBelowLimit_caches() = runTest { + supplier = createSupplier(maxPoolSize = 5) + + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) + + assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost) + } + + @Test + fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest { + supplier = createSupplier(maxPoolSize = 5) + + val viewHost = FakeWindowDecorViewHost() + val mockT = mock<SurfaceControl.Transaction>() + supplier.release(viewHost, mockT) + + assertThat(viewHost.released).isFalse() + } + + @Test + fun release_poolAtLimit_doesNotCache() = runTest { + supplier = createSupplier(maxPoolSize = 1) + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) // Maxes pool. + + val viewHost2 = FakeWindowDecorViewHost() + supplier.release(viewHost2, StubTransaction()) // Beyond limit. + + assertThat(supplier.acquire(context, context.display)).isEqualTo(viewHost) + // Second one wasn't cached, so the acquired one should've been a new instance. + assertThat(supplier.acquire(context, context.display)).isNotEqualTo(viewHost2) + } + + @Test + fun release_poolAtLimit_releasesViewHost() = runTest { + supplier = createSupplier(maxPoolSize = 1) + val viewHost = FakeWindowDecorViewHost() + supplier.release(viewHost, StubTransaction()) // Maxes pool. + + val viewHost2 = FakeWindowDecorViewHost() + val mockT = mock<SurfaceControl.Transaction>() + supplier.release(viewHost2, mockT) // Beyond limit. + + // Second one doesn't fit, so it needs to be released. + assertThat(viewHost2.released).isTrue() + } + + private fun CoroutineScope.createSupplier(maxPoolSize: Int, preWarmSize: Int = 0) = + PooledWindowDecorViewHostSupplier(context, this, testShellInit, maxPoolSize, preWarmSize) + .also { testShellInit.init() } + + private class FakeWindowDecorViewHost : WindowDecorViewHost { + var released = false + private set + + override val surfaceControl: SurfaceControl + get() = SurfaceControl() + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + onDrawTransaction: SurfaceControl.Transaction?, + ) {} + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + touchableRegion: Region?, + ) {} + + override fun release(t: SurfaceControl.Transaction) { + released = true + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..d99a4825e580 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt @@ -0,0 +1,178 @@ +/* + * 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.wm.shell.windowdecor.common.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + +/** + * Tests for [ReusableWindowDecorViewHost]. + * + * Build/Install/Run: atest WMShellUnitTests:ReusableWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class ReusableWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun update_differentView_replacesView() = runTest { + val view = View(context) + val lp = WindowManager.LayoutParams() + val reusableVH = createReusableViewHost() + reusableVH.updateView(view, lp, context.resources.configuration, null) + + assertThat(reusableVH.rootView.childCount).isEqualTo(1) + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view) + + val newView = View(context) + val newLp = WindowManager.LayoutParams() + reusableVH.updateView(newView, newLp, context.resources.configuration, null) + + assertThat(reusableVH.rootView.childCount).isEqualTo(1) + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val reusableVH = createReusableViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + reusableVH.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() + + reusableVH.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + // View host view/attrs should match the ones from the sync call. + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView) + assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val reusableVH = createReusableViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + reusableVH.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() + + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val reusableVH = createReusableViewHost() + + val view = View(context) + reusableVH.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + reusableVH.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val reusableVH = createReusableViewHost() + + val view = View(context) + reusableVH.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null, + ) + + val t = mock(SurfaceControl.Transaction::class.java) + reusableVH.release(t) + + verify(reusableVH.viewHostAdapter).release(t) + } + + @Test + fun warmUp_addsRootView() = runTest { + val reusableVH = createReusableViewHost().apply { warmUp() } + + assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() + assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView) + } + + private fun CoroutineScope.createReusableViewHost() = + ReusableWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + id = 1, + viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), + ) + + private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view +} diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index de402095e195..139ccfd22b0e 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -16,6 +16,7 @@ package com.android.extensions.appfunctions { field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 field public static final int ERROR_DENIED = 1000; // 0x3e8 field public static final int ERROR_DISABLED = 1002; // 0x3ea + field public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; // 0x7d2 field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9 field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0 diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java index 2540236f2ce5..0c521690b165 100644 --- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java @@ -71,6 +71,13 @@ public final class AppFunctionException extends Exception { public static final int ERROR_CANCELLED = 2001; /** + * The operation was disallowed by enterprise policy. + * + * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category. + */ + public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002; + + /** * An unknown error occurred while processing the call in the AppFunctionService. * * <p>This error is thrown when the service is connected in the remote application but an @@ -189,7 +196,8 @@ public final class AppFunctionException extends Exception { ERROR_SYSTEM_ERROR, ERROR_INVALID_ARGUMENT, ERROR_DISABLED, - ERROR_CANCELLED + ERROR_CANCELLED, + ERROR_ENTERPRISE_POLICY_DISALLOWED }) @Retention(RetentionPolicy.SOURCE) public @interface ErrorCode {} diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index c73598960551..d1782b285b34 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -225,8 +225,8 @@ static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong constexpr float NO_OVERRIDE = -1; -float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) { - for (const minikin::FontVariation& fv : fakery.variationSettings()) { +float findValueFromVariationSettings(const minikin::VariationSettings& axes, minikin::AxisTag tag) { + for (const minikin::FontVariation& fv : axes) { if (fv.axisTag == tag) { return fv.value; } @@ -238,8 +238,8 @@ float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin: static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); if (text_feature::typeface_redesign_readonly()) { - float value = - findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght); + float value = findValueFromVariationSettings(layout->layout.typeface(i)->GetAxes(), + minikin::TAG_wght); return std::isnan(value) ? NO_OVERRIDE : value; } else { return layout->layout.getFakery(i).wghtAdjustment(); @@ -250,8 +250,8 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); if (text_feature::typeface_redesign_readonly()) { - float value = - findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital); + float value = findValueFromVariationSettings(layout->layout.typeface(i)->GetAxes(), + minikin::TAG_ital); return std::isnan(value) ? NO_OVERRIDE : value; } else { return layout->layout.getFakery(i).italAdjustment(); diff --git a/location/api/system-current.txt b/location/api/system-current.txt index 9478e350de57..ba4224137cd4 100644 --- a/location/api/system-current.txt +++ b/location/api/system-current.txt @@ -6,6 +6,111 @@ package android.location { method public void onLocationBatch(java.util.List<android.location.Location>); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.GnssAlmanac getAlmanac(); + method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel(); + method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel(); + method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels(); + method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections(); + method @NonNull public java.util.List<android.location.BeidouSatelliteEphemeris> getSatelliteEphemeris(); + method @NonNull public java.util.List<android.location.TimeModel> getTimeModels(); + method @Nullable public android.location.UtcModel getUtcModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouAssistance> CREATOR; + } + + public static final class BeidouAssistance.Builder { + ctor public BeidouAssistance.Builder(); + method @NonNull public android.location.BeidouAssistance build(); + method @NonNull public android.location.BeidouAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac); + method @NonNull public android.location.BeidouAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel); + method @NonNull public android.location.BeidouAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel); + method @NonNull public android.location.BeidouAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>); + method @NonNull public android.location.BeidouAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>); + method @NonNull public android.location.BeidouAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.BeidouSatelliteEphemeris>); + method @NonNull public android.location.BeidouAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>); + method @NonNull public android.location.BeidouAssistance.Builder setUtcModel(@Nullable android.location.UtcModel); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class BeidouSatelliteEphemeris implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=1, to=63) public int getPrn(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel getSatelliteClockModel(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime getSatelliteEphemerisTime(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth getSatelliteHealth(); + method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris> CREATOR; + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteClockModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.00977F, to=0.00977f) public double getAf0(); + method @FloatRange(from=-1.87E-9F, to=1.87E-9f) public double getAf1(); + method @FloatRange(from=-1.39E-17F, to=1.39E-17f) public double getAf2(); + method @IntRange(from=0, to=31) public int getAodc(); + method @FloatRange(from=-5.12E-8F, to=5.12E-8f) public double getTgd1(); + method @FloatRange(from=-5.12E-8F, to=5.12E-8f) public double getTgd2(); + method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel> CREATOR; + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder { + ctor public BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel build(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setAf0(@FloatRange(from=-0.00977F, to=0.00977f) double); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setAf1(@FloatRange(from=-1.87E-9F, to=1.87E-9f) double); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setAf2(@FloatRange(from=-1.39E-17F, to=1.39E-17f) double); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setAodc(@IntRange(from=0, to=31) int); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setTgd1(@FloatRange(from=-5.12E-8F, to=5.12E-8f) double); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setTgd2(@FloatRange(from=-5.12E-8F, to=5.12E-8f) double); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0) public int getBeidouWeekNumber(); + method @IntRange(from=0, to=31) public int getIode(); + method @IntRange(from=0, to=604792) public int getToeSeconds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime> CREATOR; + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder { + ctor public BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime build(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setBeidouWeekNumber(@IntRange(from=0) int); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setIode(int); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setToeSeconds(@IntRange(from=0, to=604792) int); + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteHealth implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=1) public int getSatH1(); + method @FloatRange(from=0.0f, to=8192.0f) public double getSvAccur(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth> CREATOR; + } + + public static final class BeidouSatelliteEphemeris.BeidouSatelliteHealth.Builder { + ctor public BeidouSatelliteEphemeris.BeidouSatelliteHealth.Builder(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth build(); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth.Builder setSatH1(int); + method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth.Builder setSvAccur(double); + } + + public static final class BeidouSatelliteEphemeris.Builder { + ctor public BeidouSatelliteEphemeris.Builder(); + method @NonNull public android.location.BeidouSatelliteEphemeris build(); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setPrn(int); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteClockModel); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.BeidouSatelliteEphemeris.BeidouSatelliteHealth); + method @NonNull public android.location.BeidouSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + } + public final class CorrelationVector implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=0.0f) public double getFrequencyOffsetMetersPerSecond(); @@ -43,12 +148,375 @@ package android.location { method public void unregisterCountryDetectorCallback(@NonNull java.util.function.Consumer<android.location.Country>); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.GnssAlmanac getAlmanac(); + method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel(); + method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel(); + method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels(); + method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections(); + method @NonNull public java.util.List<android.location.GalileoSatelliteEphemeris> getSatelliteEphemeris(); + method @NonNull public java.util.List<android.location.TimeModel> getTimeModels(); + method @Nullable public android.location.UtcModel getUtcModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoAssistance> CREATOR; + } + + public static final class GalileoAssistance.Builder { + ctor public GalileoAssistance.Builder(); + method @NonNull public android.location.GalileoAssistance build(); + method @NonNull public android.location.GalileoAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac); + method @NonNull public android.location.GalileoAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel); + method @NonNull public android.location.GalileoAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel); + method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>); + method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>); + method @NonNull public android.location.GalileoAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GalileoSatelliteEphemeris>); + method @NonNull public android.location.GalileoAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>); + method @NonNull public android.location.GalileoAssistance.Builder setUtcModel(@Nullable android.location.UtcModel); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoIonosphericModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=0.0f, to=512.0f) public double getAi0(); + method @FloatRange(from=-4.0F, to=4.0f) public double getAi1(); + method @FloatRange(from=-0.5F, to=0.5f) public double getAi2(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoIonosphericModel> CREATOR; + } + + public static final class GalileoIonosphericModel.Builder { + ctor public GalileoIonosphericModel.Builder(); + method @NonNull public android.location.GalileoIonosphericModel build(); + method @NonNull public android.location.GalileoIonosphericModel.Builder setAi0(@FloatRange(from=0.0f, to=512.0f) double); + method @NonNull public android.location.GalileoIonosphericModel.Builder setAi1(@FloatRange(from=-4.0F, to=4.0f) double); + method @NonNull public android.location.GalileoIonosphericModel.Builder setAi2(@FloatRange(from=-0.5F, to=0.5f) double); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GalileoSatelliteEphemeris implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel> getSatelliteClockModels(); + method @IntRange(from=1, to=36) public int getSatelliteCodeNumber(); + method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth getSatelliteHealth(); + method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris> CREATOR; + } + + public static final class GalileoSatelliteEphemeris.Builder { + ctor public GalileoSatelliteEphemeris.Builder(); + method @NonNull public android.location.GalileoSatelliteEphemeris build(); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteClockModels(@NonNull java.util.List<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel>); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteCodeNumber(@IntRange(from=1, to=36) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GalileoSatelliteEphemeris.GalileoSvHealth); + method @NonNull public android.location.GalileoSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + } + + public static final class GalileoSatelliteEphemeris.GalileoSatelliteClockModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.0625F, to=0.0625f) public double getAf0(); + method @FloatRange(from=-1.5E-8F, to=1.5E-8f) public double getAf1(); + method @FloatRange(from=-5.56E-17F, to=5.56E-17f) public double getAf2(); + method @FloatRange(from=-1.2E-7F, to=1.2E-7f) public double getBgdSeconds(); + method public int getSatelliteClockType(); + method @FloatRange(from=0.0f) public double getSisaMeters(); + method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel> CREATOR; + field public static final int TYPE_FNAV = 1; // 0x1 + field public static final int TYPE_INAV = 2; // 0x2 + field public static final int TYPE_UNDEFINED = 0; // 0x0 + } + + public static final class GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder { + ctor public GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder(); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel build(); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setAf0(@FloatRange(from=-0.0625F, to=0.0625f) double); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setAf1(@FloatRange(from=-1.5E-8F, to=1.5E-8f) double); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setAf2(@FloatRange(from=-5.56E-17F, to=5.56E-17f) double); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setBgdSeconds(@FloatRange(from=-1.2E-7F, to=1.2E-7f) double); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setSatelliteClockType(int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setSisaMeters(@FloatRange(from=0.0f) double); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); + } + + public static final class GalileoSatelliteEphemeris.GalileoSvHealth implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=1) public int getDataValidityStatusE1b(); + method @IntRange(from=0, to=1) public int getDataValidityStatusE5a(); + method @IntRange(from=0, to=1) public int getDataValidityStatusE5b(); + method @IntRange(from=0, to=3) public int getSignalHealthStatusE1b(); + method @IntRange(from=0, to=3) public int getSignalHealthStatusE5a(); + method @IntRange(from=0, to=3) public int getSignalHealthStatusE5b(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GalileoSatelliteEphemeris.GalileoSvHealth> CREATOR; + } + + public static final class GalileoSatelliteEphemeris.GalileoSvHealth.Builder { + ctor public GalileoSatelliteEphemeris.GalileoSvHealth.Builder(); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth build(); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE1b(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5a(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setDataValidityStatusE5b(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE1b(@IntRange(from=0, to=3) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5a(@IntRange(from=0, to=3) int); + method @NonNull public android.location.GalileoSatelliteEphemeris.GalileoSvHealth.Builder setSignalHealthStatusE5b(@IntRange(from=0, to=3) int); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAlmanac implements android.os.Parcelable { + ctor public GlonassAlmanac(@IntRange(from=0) long, @NonNull java.util.List<android.location.GlonassAlmanac.GlonassSatelliteAlmanac>); + method public int describeContents(); + method @IntRange(from=0) public long getIssueDateMillis(); + method @NonNull public java.util.List<android.location.GlonassAlmanac.GlonassSatelliteAlmanac> getSatelliteAlmanacs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassAlmanac> CREATOR; + } + + public static final class GlonassAlmanac.GlonassSatelliteAlmanac implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.067F, to=0.067f) public double getDeltaI(); + method @FloatRange(from=-3600.0F, to=3600.0f) public double getDeltaT(); + method @FloatRange(from=-0.004F, to=0.004f) public double getDeltaTDot(); + method @FloatRange(from=0.0f, to=0.03f) public double getEccentricity(); + method @IntRange(from=0, to=31) public int getFreqChannel(); + method @FloatRange(from=-1.0F, to=1.0f) public double getLambda(); + method @FloatRange(from=-1.0F, to=1.0f) public double getOmega(); + method @IntRange(from=1, to=25) public int getSlotNumber(); + method @IntRange(from=0, to=1) public int getSvHealth(); + method @FloatRange(from=0.0f, to=44100.0f) public double getTLambda(); + method @FloatRange(from=-0.0019F, to=0.0019f) public double getTau(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassAlmanac.GlonassSatelliteAlmanac> CREATOR; + } + + public static final class GlonassAlmanac.GlonassSatelliteAlmanac.Builder { + ctor public GlonassAlmanac.GlonassSatelliteAlmanac.Builder(); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac build(); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaI(@FloatRange(from=-0.067F, to=0.067f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaT(@FloatRange(from=-3600.0F, to=3600.0f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setDeltaTDot(@FloatRange(from=-0.004F, to=0.004f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setEccentricity(@FloatRange(from=0.0f, to=0.03f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setFreqChannel(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setLambda(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setOmega(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSlotNumber(@IntRange(from=1, to=25) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setSvHealth(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTLambda(@FloatRange(from=0.0f, to=44100.0f) double); + method @NonNull public android.location.GlonassAlmanac.GlonassSatelliteAlmanac.Builder setTau(@FloatRange(from=-0.0019F, to=0.0019f) double); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.GlonassAlmanac getAlmanac(); + method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections(); + method @NonNull public java.util.List<android.location.GlonassSatelliteEphemeris> getSatelliteEphemeris(); + method @NonNull public java.util.List<android.location.TimeModel> getTimeModels(); + method @Nullable public android.location.UtcModel getUtcModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassAssistance> CREATOR; + } + + public static final class GlonassAssistance.Builder { + ctor public GlonassAssistance.Builder(); + method @NonNull public android.location.GlonassAssistance build(); + method @NonNull public android.location.GlonassAssistance.Builder setAlmanac(@Nullable android.location.GlonassAlmanac); + method @NonNull public android.location.GlonassAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>); + method @NonNull public android.location.GlonassAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GlonassSatelliteEphemeris>); + method @NonNull public android.location.GlonassAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>); + method @NonNull public android.location.GlonassAssistance.Builder setUtcModel(@Nullable android.location.UtcModel); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GlonassSatelliteEphemeris implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=31) public int getAgeInDays(); + method @FloatRange(from=0.0f) public double getFrameTimeSeconds(); + method @IntRange(from=0, to=1) public int getHealthState(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel getSatelliteClockModel(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel getSatelliteOrbitModel(); + method @IntRange(from=1, to=25) public int getSlotNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris> CREATOR; + } + + public static final class GlonassSatelliteEphemeris.Builder { + ctor public GlonassSatelliteEphemeris.Builder(); + method @NonNull public android.location.GlonassSatelliteEphemeris build(); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setAgeInDays(@IntRange(from=0, to=31) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setFrameTimeSeconds(@FloatRange(from=0.0f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setHealthState(@IntRange(from=0, to=1) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel); + method @NonNull public android.location.GlonassSatelliteEphemeris.Builder setSlotNumber(@IntRange(from=1, to=25) int); + } + + public static final class GlonassSatelliteEphemeris.GlonassSatelliteClockModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.002F, to=0.002f) public double getClockBias(); + method @FloatRange(from=-9.32E-10F, to=9.32E-10f) public double getFrequencyBias(); + method @IntRange(from=0xfffffff9, to=6) public int getFrequencyNumber(); + method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel> CREATOR; + } + + public static final class GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder { + ctor public GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel build(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setClockBias(@FloatRange(from=-0.002F, to=0.002f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyBias(@FloatRange(from=-9.32E-10F, to=9.32E-10f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setFrequencyNumber(@IntRange(from=0xfffffff9, to=6) int); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); + } + + public static final class GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-27000.0F, to=27000.0f) public double getX(); + method @FloatRange(from=-6.2E-9F, to=6.2E-9f) public double getXAccel(); + method @FloatRange(from=-4.3F, to=4.3f) public double getXDot(); + method @FloatRange(from=-27000.0F, to=27000.0f) public double getY(); + method @FloatRange(from=-6.2E-9F, to=6.2E-9f) public double getYAccel(); + method @FloatRange(from=-4.3F, to=4.3f) public double getYDot(); + method @FloatRange(from=-27000.0F, to=27000.0f) public double getZ(); + method @FloatRange(from=-6.2E-9F, to=6.2E-9f) public double getZAccel(); + method @FloatRange(from=-4.3F, to=4.3f) public double getZDot(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel> CREATOR; + } + + public static final class GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder { + ctor public GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel build(); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setX(@FloatRange(from=-27000.0F, to=27000.0f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setXAccel(@FloatRange(from=-6.2E-9F, to=6.2E-9f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setXDot(@FloatRange(from=-4.3F, to=4.3f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setY(@FloatRange(from=-27000.0F, to=27000.0f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setYAccel(@FloatRange(from=-6.2E-9F, to=6.2E-9f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setYDot(@FloatRange(from=-4.3F, to=4.3f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setZ(@FloatRange(from=-27000.0F, to=27000.0f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setZAccel(@FloatRange(from=-6.2E-9F, to=6.2E-9f) double); + method @NonNull public android.location.GlonassSatelliteEphemeris.GlonassSatelliteOrbitModel.Builder setZDot(@FloatRange(from=-4.3F, to=4.3f) double); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GnssAlmanac implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac> getGnssSatelliteAlmanacs(); + method @IntRange(from=0) public int getIod(); + method @IntRange(from=0) public long getIssueDateMillis(); + method @IntRange(from=0, to=604800) public int getToaSeconds(); + method @IntRange(from=0) public int getWeekNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAlmanac> CREATOR; + } + + public static final class GnssAlmanac.Builder { + ctor public GnssAlmanac.Builder(); + method @NonNull public android.location.GnssAlmanac build(); + method @NonNull public android.location.GnssAlmanac.Builder setGnssSatelliteAlmanacs(@NonNull java.util.List<android.location.GnssAlmanac.GnssSatelliteAlmanac>); + method @NonNull public android.location.GnssAlmanac.Builder setIod(@IntRange(from=0) int); + method @NonNull public android.location.GnssAlmanac.Builder setIssueDateMillis(@IntRange(from=0) long); + method @NonNull public android.location.GnssAlmanac.Builder setToaSeconds(@IntRange(from=0, to=604800) int); + method @NonNull public android.location.GnssAlmanac.Builder setWeekNumber(@IntRange(from=0) int); + } + + public static final class GnssAlmanac.GnssSatelliteAlmanac implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.0625F, to=0.0625f) public double getAf0(); + method @FloatRange(from=-1.5E-8F, to=1.5E-8f) public double getAf1(); + method @FloatRange(from=0.0f) public double getEccentricity(); + method @FloatRange(from=-1.0F, to=1.0f) public double getInclination(); + method @FloatRange(from=-1.0F, to=1.0f) public double getM0(); + method @FloatRange(from=-1.0F, to=1.0f) public double getOmega(); + method @FloatRange(from=-1.0F, to=1.0f) public double getOmega0(); + method @FloatRange(from=-1.0F, to=1.0f) public double getOmegaDot(); + method @FloatRange(from=0.0f, to=8192.0f) public double getRootA(); + method @IntRange(from=0) public int getSvHealth(); + method @IntRange(from=1) public int getSvid(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAlmanac.GnssSatelliteAlmanac> CREATOR; + } + + public static final class GnssAlmanac.GnssSatelliteAlmanac.Builder { + ctor public GnssAlmanac.GnssSatelliteAlmanac.Builder(); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac build(); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setAf0(@FloatRange(from=-0.0625F, to=0.0625f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setAf1(@FloatRange(from=-1.5E-8F, to=1.5E-8f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setEccentricity(@FloatRange(from=0.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setInclination(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setM0(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setOmega(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setOmega0(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setOmegaDot(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setRootA(@FloatRange(from=0.0f, to=8192.0f) double); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setSvHealth(@IntRange(from=0) int); + method @NonNull public android.location.GnssAlmanac.GnssSatelliteAlmanac.Builder setSvid(@IntRange(from=1) int); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GnssAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.BeidouAssistance getBeidouAssistance(); + method @Nullable public android.location.GalileoAssistance getGalileoAssistance(); + method @Nullable public android.location.GlonassAssistance getGlonassAssistance(); + method @Nullable public android.location.GpsAssistance getGpsAssistance(); + method @Nullable public android.location.QzssAssistance getQzssAssistance(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAssistance> CREATOR; + } + + public static final class GnssAssistance.Builder { + ctor public GnssAssistance.Builder(); + method @NonNull public android.location.GnssAssistance build(); + method @NonNull public android.location.GnssAssistance.Builder setBeidouAssistance(@Nullable android.location.BeidouAssistance); + method @NonNull public android.location.GnssAssistance.Builder setGalileoAssistance(@Nullable android.location.GalileoAssistance); + method @NonNull public android.location.GnssAssistance.Builder setGlonassAssistance(@Nullable android.location.GlonassAssistance); + method @NonNull public android.location.GnssAssistance.Builder setGpsAssistance(@Nullable android.location.GpsAssistance); + method @NonNull public android.location.GnssAssistance.Builder setQzssAssistance(@Nullable android.location.QzssAssistance); + } + + public static final class GnssAssistance.GnssSatelliteCorrections implements android.os.Parcelable { + ctor public GnssAssistance.GnssSatelliteCorrections(@IntRange(from=1, to=206) int, @NonNull java.util.List<android.location.IonosphericCorrection>); + method public int describeContents(); + method @NonNull public java.util.List<android.location.IonosphericCorrection> getIonosphericCorrections(); + method @IntRange(from=1, to=206) public int getSvid(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssAssistance.GnssSatelliteCorrections> CREATOR; + } + public final class GnssCapabilities implements android.os.Parcelable { method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane(); method @Deprecated public boolean hasNavMessages(); method @Deprecated public boolean hasSatelliteBlacklist(); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GnssCorrectionComponent implements android.os.Parcelable { + ctor public GnssCorrectionComponent(@NonNull String, @NonNull android.location.GnssCorrectionComponent.GnssInterval, @NonNull android.location.GnssCorrectionComponent.PseudorangeCorrection); + method public int describeContents(); + method @NonNull public android.location.GnssCorrectionComponent.PseudorangeCorrection getPseudorangeCorrection(); + method @NonNull public String getSourceKey(); + method @NonNull public android.location.GnssCorrectionComponent.GnssInterval getValidityInterval(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCorrectionComponent> CREATOR; + } + + public static final class GnssCorrectionComponent.GnssInterval implements android.os.Parcelable { + ctor public GnssCorrectionComponent.GnssInterval(@IntRange(from=0) long, @IntRange(from=0) long); + method public int describeContents(); + method @IntRange(from=0) public long getEndMillisSinceGpsEpoch(); + method @IntRange(from=0) public long getStartMillisSinceGpsEpoch(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCorrectionComponent.GnssInterval> CREATOR; + } + + public static final class GnssCorrectionComponent.PseudorangeCorrection implements android.os.Parcelable { + ctor public GnssCorrectionComponent.PseudorangeCorrection(double, double, double); + method public int describeContents(); + method public double getCorrectionMeters(); + method public double getCorrectionRateMetersPerSecond(); + method @FloatRange(from=0.0f) public double getCorrectionUncertaintyMeters(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCorrectionComponent.PseudorangeCorrection> CREATOR; + } + public final class GnssExcessPathInfo implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=0.0f) public float getAttenuationDb(); @@ -193,6 +661,33 @@ package android.location { method @NonNull public android.location.GnssSingleSatCorrection.Builder setSatelliteId(@IntRange(from=0) int); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.GnssAlmanac getAlmanac(); + method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel(); + method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel(); + method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels(); + method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections(); + method @NonNull public java.util.List<android.location.GpsSatelliteEphemeris> getSatelliteEphemeris(); + method @NonNull public java.util.List<android.location.TimeModel> getTimeModels(); + method @Nullable public android.location.UtcModel getUtcModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsAssistance> CREATOR; + } + + public static final class GpsAssistance.Builder { + ctor public GpsAssistance.Builder(); + method @NonNull public android.location.GpsAssistance build(); + method @NonNull public android.location.GpsAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac); + method @NonNull public android.location.GpsAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel); + method @NonNull public android.location.GpsAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel); + method @NonNull public android.location.GpsAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>); + method @NonNull public android.location.GpsAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>); + method @NonNull public android.location.GpsAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.GpsSatelliteEphemeris>); + method @NonNull public android.location.GpsAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>); + method @NonNull public android.location.GpsAssistance.Builder setUtcModel(@Nullable android.location.UtcModel); + } + @Deprecated public class GpsClock implements android.os.Parcelable { method @Deprecated public int describeContents(); method @Deprecated public double getBiasInNs(); @@ -418,6 +913,174 @@ package android.location { method @Deprecated public void onStatusChanged(int); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class GpsSatelliteEphemeris implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); + method @IntRange(from=1, to=32) public int getPrn(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); + method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); + method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris> CREATOR; + } + + public static final class GpsSatelliteEphemeris.Builder { + ctor public GpsSatelliteEphemeris.Builder(); + method @NonNull public android.location.GpsSatelliteEphemeris build(); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setPrn(@IntRange(from=1, to=32) int); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); + method @NonNull public android.location.GpsSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + } + + public static final class GpsSatelliteEphemeris.GpsL2Params implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=3) public int getL2Code(); + method @IntRange(from=0, to=1) public int getL2Flag(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris.GpsL2Params> CREATOR; + } + + public static final class GpsSatelliteEphemeris.GpsL2Params.Builder { + ctor public GpsSatelliteEphemeris.GpsL2Params.Builder(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params build(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params.Builder setL2Code(@IntRange(from=0, to=3) int); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params.Builder setL2Flag(@IntRange(from=0, to=1) int); + } + + public static final class GpsSatelliteEphemeris.GpsSatelliteClockModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-0.00977F, to=0.00977f) public double getAf0(); + method @FloatRange(from=-3.73E-9F, to=3.73E-9f) public double getAf1(); + method @FloatRange(from=-3.56E-15F, to=3.56E-15f) public double getAf2(); + method @IntRange(from=0, to=1023) public int getIodc(); + method @FloatRange(from=-5.97E-8F, to=5.97E-8f) public double getTgd(); + method @IntRange(from=0) public long getTimeOfClockSeconds(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel> CREATOR; + } + + public static final class GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder { + ctor public GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel build(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setAf0(@FloatRange(from=-0.00977F, to=0.00977f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setAf1(@FloatRange(from=-3.73E-9F, to=3.73E-9f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setAf2(@FloatRange(from=-3.56E-15F, to=3.56E-15f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setIodc(@IntRange(from=0, to=1023) int); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setTgd(@FloatRange(from=-5.97E-8F, to=5.97E-8f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel.Builder setTimeOfClockSeconds(@IntRange(from=0) long); + } + + public static final class GpsSatelliteEphemeris.GpsSatelliteHealth implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=0.0f) public double getFitInt(); + method @FloatRange(from=0.0f, to=8192.0f) public double getSvAccur(); + method @IntRange(from=0, to=63) public int getSvHealth(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GpsSatelliteEphemeris.GpsSatelliteHealth> CREATOR; + } + + public static final class GpsSatelliteEphemeris.GpsSatelliteHealth.Builder { + ctor public GpsSatelliteEphemeris.GpsSatelliteHealth.Builder(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth build(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth.Builder setFitInt(@FloatRange(from=0.0f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth.Builder setSvAccur(@FloatRange(from=0.0f, to=8192.0f) double); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth.Builder setSvHealth(@IntRange(from=0, to=63) int); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class IonosphericCorrection implements android.os.Parcelable { + ctor public IonosphericCorrection(@IntRange(from=0) long, @NonNull android.location.GnssCorrectionComponent); + method public int describeContents(); + method @IntRange(from=0) public long getCarrierFrequencyHz(); + method @NonNull public android.location.GnssCorrectionComponent getIonosphericCorrection(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.IonosphericCorrection> CREATOR; + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class KeplerianOrbitModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-1.18E-8F, to=1.18E-8f) public double getDeltaN(); + method @FloatRange(from=0.0f, to=0.5f) public double getEccentricity(); + method @FloatRange(from=-3.15F, to=3.15f) public double getI0(); + method @FloatRange(from=-2.94E-9F, to=2.94E-9f) public double getIDot(); + method @FloatRange(from=-3.15F, to=3.15f) public double getM0(); + method @FloatRange(from=-3.15F, to=3.15f) public double getOmega(); + method @FloatRange(from=-3.15F, to=3.15f) public double getOmega0(); + method @FloatRange(from=-3.1E-6F, to=3.1E-6f) public double getOmegaDot(); + method @FloatRange(from=0.0f, to=8192.0f) public double getRootA(); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation getSecondOrderHarmonicPerturbation(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.KeplerianOrbitModel> CREATOR; + } + + public static final class KeplerianOrbitModel.Builder { + ctor public KeplerianOrbitModel.Builder(); + method @NonNull public android.location.KeplerianOrbitModel build(); + method @NonNull public android.location.KeplerianOrbitModel.Builder setDeltaN(@FloatRange(from=-1.18E-8F, to=1.18E-8f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setEccentricity(@FloatRange(from=0.0f, to=0.5f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setI0(@FloatRange(from=-3.15F, to=3.15f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setIDot(@FloatRange(from=-2.94E-9F, to=2.94E-9f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setM0(@FloatRange(from=-3.15F, to=3.15f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setOmega(@FloatRange(from=-3.15F, to=3.15f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setOmega0(@FloatRange(from=-3.15F, to=3.15f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setOmegaDot(@FloatRange(from=-3.1E-6F, to=3.1E-6f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setRootA(@FloatRange(from=0.0f, to=8192.0f) double); + method @NonNull public android.location.KeplerianOrbitModel.Builder setSecondOrderHarmonicPerturbation(@NonNull android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation); + } + + public static final class KeplerianOrbitModel.SecondOrderHarmonicPerturbation implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-6.11E-5F, to=6.11E-5f) public double getCic(); + method @FloatRange(from=-6.11E-5F, to=6.11E-5f) public double getCis(); + method @FloatRange(from=-2048.0F, to=2048.0f) public double getCrc(); + method @FloatRange(from=-2048.0F, to=2048.0f) public double getCrs(); + method @FloatRange(from=-6.11E-5F, to=6.11E-5f) public double getCuc(); + method @FloatRange(from=-6.11E-5F, to=6.11E-5f) public double getCus(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation> CREATOR; + } + + public static final class KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder { + ctor public KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder(); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation build(); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCic(@FloatRange(from=-6.11E-5F, to=6.11E-5f) double); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCis(@FloatRange(from=-6.11E-5F, to=6.11E-5f) double); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCrc(@FloatRange(from=-2048.0F, to=2048.0f) double); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCrs(@FloatRange(from=-2048.0F, to=2048.0f) double); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCuc(@FloatRange(from=-6.11E-5F, to=6.11E-5f) double); + method @NonNull public android.location.KeplerianOrbitModel.SecondOrderHarmonicPerturbation.Builder setCus(@FloatRange(from=-6.11E-5F, to=6.11E-5f) double); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class KlobucharIonosphericModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-1.193E-7F, to=1.193E-7f) public double getAlpha0(); + method @FloatRange(from=-9.54E-7F, to=9.54E-7f) public double getAlpha1(); + method @FloatRange(from=-7.63E-6F, to=7.63E-6f) public double getAlpha2(); + method @FloatRange(from=-7.63E-6F, to=7.63E-6f) public double getAlpha3(); + method @FloatRange(from=-262144.0F, to=262144.0f) public double getBeta0(); + method @FloatRange(from=-2097152.0F, to=2097152.0f) public double getBeta1(); + method @FloatRange(from=-8388608.0F, to=8388608.0f) public double getBeta2(); + method @FloatRange(from=-8388608.0F, to=8388608.0f) public double getBeta3(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.KlobucharIonosphericModel> CREATOR; + } + + public static final class KlobucharIonosphericModel.Builder { + ctor public KlobucharIonosphericModel.Builder(); + method @NonNull public android.location.KlobucharIonosphericModel build(); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setAlpha0(@FloatRange(from=-1.193E-7F, to=1.193E-7f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setAlpha1(@FloatRange(from=-9.54E-7F, to=9.54E-7f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setAlpha2(@FloatRange(from=-7.63E-6F, to=7.63E-6f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setAlpha3(@FloatRange(from=-7.63E-6F, to=7.63E-6f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setBeta0(@FloatRange(from=-262144.0F, to=262144.0f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setBeta1(@FloatRange(from=-2097152.0F, to=2097152.0f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setBeta2(@FloatRange(from=-8388608.0F, to=8388608.0f) double); + method @NonNull public android.location.KlobucharIonosphericModel.Builder setBeta3(@FloatRange(from=-8388608.0F, to=8388608.0f) double); + } + public final class LastLocationRequest implements android.os.Parcelable { method public int describeContents(); method public boolean isAdasGnssBypass(); @@ -436,6 +1099,25 @@ package android.location { method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_BYPASS) public android.location.LastLocationRequest.Builder setLocationSettingsIgnored(boolean); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class LeapSecondsModel implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0) public int getDayNumberLeapSecondsFuture(); + method @IntRange(from=0) public int getLeapSeconds(); + method @IntRange(from=0) public int getLeapSecondsFuture(); + method @IntRange(from=0) public int getWeekNumberLeapSecondsFuture(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.LeapSecondsModel> CREATOR; + } + + public static final class LeapSecondsModel.Builder { + ctor public LeapSecondsModel.Builder(); + method @NonNull public android.location.LeapSecondsModel build(); + method @NonNull public android.location.LeapSecondsModel.Builder setDayNumberLeapSecondsFuture(@IntRange(from=0) int); + method @NonNull public android.location.LeapSecondsModel.Builder setLeapSeconds(@IntRange(from=0) int); + method @NonNull public android.location.LeapSecondsModel.Builder setLeapSecondsFuture(@IntRange(from=0) int); + method @NonNull public android.location.LeapSecondsModel.Builder setWeekNumberLeapSecondsFuture(@IntRange(from=0) int); + } + public class LocationManager { method @Deprecated @FlaggedApi("android.location.flags.deprecate_provider_request_apis") @RequiresPermission(allOf={android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void addProviderRequestChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.location.provider.ProviderRequest.ChangedListener); method @Deprecated @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void flushGnssBatch(); @@ -513,6 +1195,98 @@ package android.location { method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.LocationRequest.Builder setWorkSource(@Nullable android.os.WorkSource); } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssAssistance implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.location.GnssAlmanac getAlmanac(); + method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel(); + method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel(); + method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels(); + method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections(); + method @NonNull public java.util.List<android.location.QzssSatelliteEphemeris> getSatelliteEphemeris(); + method @NonNull public java.util.List<android.location.TimeModel> getTimeModels(); + method @Nullable public android.location.UtcModel getUtcModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.QzssAssistance> CREATOR; + } + + public static final class QzssAssistance.Builder { + ctor public QzssAssistance.Builder(); + method @NonNull public android.location.QzssAssistance build(); + method @NonNull public android.location.QzssAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac); + method @NonNull public android.location.QzssAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel); + method @NonNull public android.location.QzssAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel); + method @NonNull public android.location.QzssAssistance.Builder setRealTimeIntegrityModels(@Nullable java.util.List<android.location.RealTimeIntegrityModel>); + method @NonNull public android.location.QzssAssistance.Builder setSatelliteCorrections(@Nullable java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>); + method @NonNull public android.location.QzssAssistance.Builder setSatelliteEphemeris(@Nullable java.util.List<android.location.QzssSatelliteEphemeris>); + method @NonNull public android.location.QzssAssistance.Builder setTimeModels(@Nullable java.util.List<android.location.TimeModel>); + method @NonNull public android.location.QzssAssistance.Builder setUtcModel(@Nullable android.location.UtcModel); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class QzssSatelliteEphemeris implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsL2Params getGpsL2Params(); + method @IntRange(from=183, to=206) public int getPrn(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel getSatelliteClockModel(); + method @NonNull public android.location.SatelliteEphemerisTime getSatelliteEphemerisTime(); + method @NonNull public android.location.GpsSatelliteEphemeris.GpsSatelliteHealth getSatelliteHealth(); + method @NonNull public android.location.KeplerianOrbitModel getSatelliteOrbitModel(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.QzssSatelliteEphemeris> CREATOR; + } + + public static final class QzssSatelliteEphemeris.Builder { + ctor public QzssSatelliteEphemeris.Builder(); + method @NonNull public android.location.QzssSatelliteEphemeris build(); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setGpsL2Params(@NonNull android.location.GpsSatelliteEphemeris.GpsL2Params); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setPrn(@IntRange(from=183, to=206) int); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteClockModel(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteEphemerisTime(@NonNull android.location.SatelliteEphemerisTime); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteHealth(@NonNull android.location.GpsSatelliteEphemeris.GpsSatelliteHealth); + method @NonNull public android.location.QzssSatelliteEphemeris.Builder setSatelliteOrbitModel(@NonNull android.location.KeplerianOrbitModel); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class RealTimeIntegrityModel implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getAdvisoryNumber(); + method @NonNull public String getAdvisoryType(); + method @IntRange(from=0) public long getEndDateSeconds(); + method @IntRange(from=0) public long getPublishDateSeconds(); + method @IntRange(from=0) public long getStartDateSeconds(); + method @IntRange(from=1, to=206) public int getSvid(); + method public boolean isUsable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.RealTimeIntegrityModel> CREATOR; + } + + public static final class RealTimeIntegrityModel.Builder { + ctor public RealTimeIntegrityModel.Builder(); + method @NonNull public android.location.RealTimeIntegrityModel build(); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryNumber(@NonNull String); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setAdvisoryType(@NonNull String); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setEndDateSeconds(@IntRange(from=0) long); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setPublishDateSeconds(@IntRange(from=0) long); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setStartDateSeconds(@IntRange(from=0) long); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setSvid(@IntRange(from=1, to=206) int); + method @NonNull public android.location.RealTimeIntegrityModel.Builder setUsable(boolean); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class SatelliteEphemerisTime implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=1023) public int getIode(); + method @IntRange(from=0, to=604799) public int getToeSeconds(); + method @IntRange(from=0) public int getWeekNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.SatelliteEphemerisTime> CREATOR; + } + + public static final class SatelliteEphemerisTime.Builder { + ctor public SatelliteEphemerisTime.Builder(); + method @NonNull public android.location.SatelliteEphemerisTime build(); + method @NonNull public android.location.SatelliteEphemerisTime.Builder setIode(@IntRange(from=0, to=1023) int); + method @NonNull public android.location.SatelliteEphemerisTime.Builder setToeSeconds(@IntRange(from=0, to=604799) int); + method @NonNull public android.location.SatelliteEphemerisTime.Builder setWeekNumber(@IntRange(from=0) int); + } + public final class SatellitePvt implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.location.SatellitePvt.ClockInfo getClockInfo(); @@ -587,6 +1361,46 @@ package android.location { field @NonNull public static final android.os.Parcelable.Creator<android.location.SatellitePvt.VelocityEcef> CREATOR; } + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class TimeModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-1.0F, to=1.0f) public double getA0(); + method @FloatRange(from=-3.28E-6F, to=3.28E-6f) public double getA1(); + method @IntRange(from=0, to=604800) public int getTimeOfWeek(); + method public int getToGnss(); + method @IntRange(from=0) public int getWeekNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.TimeModel> CREATOR; + } + + public static final class TimeModel.Builder { + ctor public TimeModel.Builder(); + method @NonNull public android.location.TimeModel build(); + method @NonNull public android.location.TimeModel.Builder setA0(@FloatRange(from=-1.0F, to=1.0f) double); + method @NonNull public android.location.TimeModel.Builder setA1(@FloatRange(from=-3.28E-6F, to=3.28E-6f) double); + method @NonNull public android.location.TimeModel.Builder setTimeOfWeek(@IntRange(from=0, to=604800) int); + method @NonNull public android.location.TimeModel.Builder setToGnss(int); + method @NonNull public android.location.TimeModel.Builder setWeekNumber(@IntRange(from=0) int); + } + + @FlaggedApi("android.location.flags.gnss_assistance_interface") public final class UtcModel implements android.os.Parcelable { + method public int describeContents(); + method @FloatRange(from=-2.0F, to=2.0f) public double getA0(); + method @FloatRange(from=-7.45E-9F, to=7.45E-9f) public double getA1(); + method @IntRange(from=0, to=604800) public int getTimeOfWeek(); + method @IntRange(from=0) public int getWeekNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.UtcModel> CREATOR; + } + + public static final class UtcModel.Builder { + ctor public UtcModel.Builder(); + method @NonNull public android.location.UtcModel build(); + method @NonNull public android.location.UtcModel.Builder setA0(@FloatRange(from=-2.0F, to=2.0f) double); + method @NonNull public android.location.UtcModel.Builder setA1(@FloatRange(from=-7.45E-9F, to=7.45E-9f) double); + method @NonNull public android.location.UtcModel.Builder setTimeOfWeek(@IntRange(from=0, to=604800) int); + method @NonNull public android.location.UtcModel.Builder setWeekNumber(@IntRange(from=0) int); + } + } package android.location.provider { diff --git a/location/java/android/location/BeidouAssistance.java b/location/java/android/location/BeidouAssistance.java new file mode 100644 index 000000000000..f55249e605a0 --- /dev/null +++ b/location/java/android/location/BeidouAssistance.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.location.GnssAssistance.GnssSatelliteCorrections; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains Beidou assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class BeidouAssistance implements Parcelable { + + /** The Beidou almanac. */ + @Nullable private final GnssAlmanac mAlmanac; + + /** The Klobuchar ionospheric model. */ + @Nullable private final KlobucharIonosphericModel mIonosphericModel; + + /** The UTC model. */ + @Nullable private final UtcModel mUtcModel; + + /** The leap seconds model. */ + @Nullable private final LeapSecondsModel mLeapSecondsModel; + + /** The list of time models. */ + @NonNull private final List<TimeModel> mTimeModels; + + /** The list of Beidou ephemeris. */ + @NonNull private final List<BeidouSatelliteEphemeris> mSatelliteEphemeris; + + /** The list of real time integrity models. */ + @NonNull private final List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + + /** The list of Beidou satellite corrections. */ + @NonNull private final List<GnssSatelliteCorrections> mSatelliteCorrections; + + private BeidouAssistance(Builder builder) { + mAlmanac = builder.mAlmanac; + mIonosphericModel = builder.mIonosphericModel; + mUtcModel = builder.mUtcModel; + mLeapSecondsModel = builder.mLeapSecondsModel; + if (builder.mTimeModels != null) { + mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels)); + } else { + mTimeModels = new ArrayList<>(); + } + if (builder.mSatelliteEphemeris != null) { + mSatelliteEphemeris = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteEphemeris)); + } else { + mSatelliteEphemeris = new ArrayList<>(); + } + if (builder.mRealTimeIntegrityModels != null) { + mRealTimeIntegrityModels = + Collections.unmodifiableList(new ArrayList<>(builder.mRealTimeIntegrityModels)); + } else { + mRealTimeIntegrityModels = new ArrayList<>(); + } + if (builder.mSatelliteCorrections != null) { + mSatelliteCorrections = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteCorrections)); + } else { + mSatelliteCorrections = new ArrayList<>(); + } + } + + /** Returns the Beidou almanac. */ + @Nullable + public GnssAlmanac getAlmanac() { + return mAlmanac; + } + + /** Returns the Klobuchar ionospheric model. */ + @Nullable + public KlobucharIonosphericModel getIonosphericModel() { + return mIonosphericModel; + } + + /** Returns the UTC model. */ + @Nullable + public UtcModel getUtcModel() { + return mUtcModel; + } + + /** Returns the leap seconds model. */ + @Nullable + public LeapSecondsModel getLeapSecondsModel() { + return mLeapSecondsModel; + } + + /** Returns the list of time models. */ + @NonNull + public List<TimeModel> getTimeModels() { + return mTimeModels; + } + + /** Returns the list ofBeidou ephemeris. */ + @NonNull + public List<BeidouSatelliteEphemeris> getSatelliteEphemeris() { + return mSatelliteEphemeris; + } + + /** Returns the list of real time integrity models. */ + @NonNull + public List<RealTimeIntegrityModel> getRealTimeIntegrityModels() { + return mRealTimeIntegrityModels; + } + + /** Returns the list of Beidou satellite corrections. */ + @NonNull + public List<GnssSatelliteCorrections> getSatelliteCorrections() { + return mSatelliteCorrections; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("BeidouAssistance["); + builder.append("almanac = ").append(mAlmanac); + builder.append(", ionosphericModel = ").append(mIonosphericModel); + builder.append(", utcModel = ").append(mUtcModel); + builder.append(", leapSecondsModel = ").append(mLeapSecondsModel); + builder.append(", timeModels = ").append(mTimeModels); + builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris); + builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels); + builder.append(", satelliteCorrections = ").append(mSatelliteCorrections); + builder.append("]"); + return builder.toString(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mAlmanac, flags); + dest.writeTypedObject(mIonosphericModel, flags); + dest.writeTypedObject(mUtcModel, flags); + dest.writeTypedObject(mLeapSecondsModel, flags); + dest.writeTypedList(mTimeModels); + dest.writeTypedList(mSatelliteEphemeris); + dest.writeTypedList(mRealTimeIntegrityModels); + dest.writeTypedList(mSatelliteCorrections); + } + + public static final @android.annotation.NonNull Creator<BeidouAssistance> CREATOR = + new Creator<BeidouAssistance>() { + @Override + public BeidouAssistance createFromParcel(Parcel in) { + return new BeidouAssistance.Builder() + .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR)) + .setIonosphericModel( + in.readTypedObject(KlobucharIonosphericModel.CREATOR)) + .setUtcModel(in.readTypedObject(UtcModel.CREATOR)) + .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR)) + .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR)) + .setSatelliteEphemeris( + in.createTypedArrayList(BeidouSatelliteEphemeris.CREATOR)) + .setRealTimeIntegrityModels( + in.createTypedArrayList(RealTimeIntegrityModel.CREATOR)) + .setSatelliteCorrections( + in.createTypedArrayList(GnssSatelliteCorrections.CREATOR)) + .build(); + } + + @Override + public BeidouAssistance[] newArray(int size) { + return new BeidouAssistance[size]; + } + }; + + /** Builder for {@link BeidouAssistance}. */ + public static final class Builder { + private GnssAlmanac mAlmanac; + private KlobucharIonosphericModel mIonosphericModel; + private UtcModel mUtcModel; + private LeapSecondsModel mLeapSecondsModel; + private List<TimeModel> mTimeModels; + private List<BeidouSatelliteEphemeris> mSatelliteEphemeris; + private List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + private List<GnssSatelliteCorrections> mSatelliteCorrections; + + /** Sets the Beidou almanac. */ + @NonNull + public Builder setAlmanac(@Nullable GnssAlmanac almanac) { + mAlmanac = almanac; + return this; + } + + /** Sets the Klobuchar ionospheric model. */ + @NonNull + public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) { + mIonosphericModel = ionosphericModel; + return this; + } + + /** Sets the UTC model. */ + @NonNull + public Builder setUtcModel(@Nullable UtcModel utcModel) { + mUtcModel = utcModel; + return this; + } + + /** Sets the leap seconds model. */ + @NonNull + public Builder setLeapSecondsModel(@Nullable LeapSecondsModel leapSecondsModel) { + mLeapSecondsModel = leapSecondsModel; + return this; + } + + /** Sets the list of time models. */ + @NonNull + public Builder setTimeModels( + @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) { + mTimeModels = timeModels; + return this; + } + + /** Sets the list of Beidou ephemeris. */ + @NonNull + public Builder setSatelliteEphemeris( + @Nullable @SuppressLint("NullableCollection") + List<BeidouSatelliteEphemeris> satelliteEphemeris) { + mSatelliteEphemeris = satelliteEphemeris; + return this; + } + + /** Sets the list of real time integrity models. */ + @NonNull + public Builder setRealTimeIntegrityModels( + @Nullable @SuppressLint("NullableCollection") + List<RealTimeIntegrityModel> realTimeIntegrityModels) { + mRealTimeIntegrityModels = realTimeIntegrityModels; + return this; + } + + /** Sets the list of Beidou satellite corrections. */ + @NonNull + public Builder setSatelliteCorrections( + @Nullable @SuppressLint("NullableCollection") + List<GnssSatelliteCorrections> satelliteCorrections) { + mSatelliteCorrections = satelliteCorrections; + return this; + } + + /** Builds the {@link BeidouAssistance}. */ + @NonNull + public BeidouAssistance build() { + return new BeidouAssistance(this); + } + } +} diff --git a/location/java/android/location/BeidouSatelliteEphemeris.java b/location/java/android/location/BeidouSatelliteEphemeris.java new file mode 100644 index 000000000000..6bd91e3318c3 --- /dev/null +++ b/location/java/android/location/BeidouSatelliteEphemeris.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains ephemeris parameters specific to Beidou satellites. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class BeidouSatelliteEphemeris implements Parcelable { + /** The PRN number of the Beidou satellite. */ + private final int mPrn; + + /** Satellite clock model. */ + private final BeidouSatelliteClockModel mSatelliteClockModel; + + /** Satellite orbit model. */ + private final KeplerianOrbitModel mSatelliteOrbitModel; + + /** Satellite health. */ + private final BeidouSatelliteHealth mSatelliteHealth; + + /** Satellite ephemeris time. */ + private final BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; + + private BeidouSatelliteEphemeris(Builder builder) { + // Allow PRN beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mPrn >= 1); + Preconditions.checkNotNull(builder.mSatelliteClockModel, + "SatelliteClockModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteOrbitModel, + "SatelliteOrbitModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteHealth, + "SatelliteHealth cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, + "SatelliteEphemerisTime cannot be null"); + mPrn = builder.mPrn; + mSatelliteClockModel = builder.mSatelliteClockModel; + mSatelliteOrbitModel = builder.mSatelliteOrbitModel; + mSatelliteHealth = builder.mSatelliteHealth; + mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; + } + + /** Returns the PRN of the satellite. */ + @IntRange(from = 1, to = 63) + public int getPrn() { + return mPrn; + } + + /** Returns the satellite clock model. */ + @NonNull + public BeidouSatelliteClockModel getSatelliteClockModel() { + return mSatelliteClockModel; + } + + /** Returns the satellite orbit model. */ + @NonNull + public KeplerianOrbitModel getSatelliteOrbitModel() { + return mSatelliteOrbitModel; + } + + /** Returns the satellite health. */ + @NonNull + public BeidouSatelliteHealth getSatelliteHealth() { + return mSatelliteHealth; + } + + /** Returns the satellite ephemeris time. */ + @NonNull + public BeidouSatelliteEphemerisTime getSatelliteEphemerisTime() { + return mSatelliteEphemerisTime; + } + + public static final @NonNull Creator<BeidouSatelliteEphemeris> CREATOR = + new Creator<BeidouSatelliteEphemeris>() { + @Override + @NonNull + public BeidouSatelliteEphemeris createFromParcel(Parcel in) { + final BeidouSatelliteEphemeris.Builder beidouSatelliteEphemeris = + new Builder() + .setPrn(in.readInt()) + .setSatelliteClockModel( + in.readTypedObject(BeidouSatelliteClockModel.CREATOR)) + .setSatelliteOrbitModel( + in.readTypedObject(KeplerianOrbitModel.CREATOR)) + .setSatelliteHealth( + in.readTypedObject(BeidouSatelliteHealth.CREATOR)) + .setSatelliteEphemerisTime( + in.readTypedObject( + BeidouSatelliteEphemerisTime.CREATOR)); + return beidouSatelliteEphemeris.build(); + } + + @Override + public BeidouSatelliteEphemeris[] newArray(int size) { + return new BeidouSatelliteEphemeris[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mPrn); + parcel.writeTypedObject(mSatelliteClockModel, flags); + parcel.writeTypedObject(mSatelliteOrbitModel, flags); + parcel.writeTypedObject(mSatelliteHealth, flags); + parcel.writeTypedObject(mSatelliteEphemerisTime, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("BeidouSatelliteEphemeris["); + builder.append("prn = ").append(mPrn); + builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); + builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); + builder.append(", satelliteHealth = ").append(mSatelliteHealth); + builder.append(", satelliteEphemerisTime = ").append(mSatelliteEphemerisTime); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link BeidouSatelliteEphemeris} */ + public static final class Builder { + private int mPrn; + private BeidouSatelliteClockModel mSatelliteClockModel; + private KeplerianOrbitModel mSatelliteOrbitModel; + private BeidouSatelliteHealth mSatelliteHealth; + private BeidouSatelliteEphemerisTime mSatelliteEphemerisTime; + + /** Sets the PRN of the satellite. */ + @NonNull + public Builder setPrn(int prn) { + mPrn = prn; + return this; + } + + /** Sets the satellite clock model. */ + @NonNull + public Builder setSatelliteClockModel( + @NonNull BeidouSatelliteClockModel satelliteClockModel) { + mSatelliteClockModel = satelliteClockModel; + return this; + } + + /** Sets the satellite orbit model. */ + @NonNull + public Builder setSatelliteOrbitModel(@NonNull KeplerianOrbitModel satelliteOrbitModel) { + mSatelliteOrbitModel = satelliteOrbitModel; + return this; + } + + /** Sets the satellite health. */ + @NonNull + public Builder setSatelliteHealth(@NonNull BeidouSatelliteHealth satelliteHealth) { + mSatelliteHealth = satelliteHealth; + return this; + } + + /** Sets the satellite ephemeris time. */ + @NonNull + public Builder setSatelliteEphemerisTime( + @NonNull BeidouSatelliteEphemerisTime satelliteEphemerisTime) { + mSatelliteEphemerisTime = satelliteEphemerisTime; + return this; + } + + /** Builds a {@link BeidouSatelliteEphemeris} instance as specified by this builder. */ + @NonNull + public BeidouSatelliteEphemeris build() { + return new BeidouSatelliteEphemeris(this); + } + } + + /** + * A class contains the set of parameters needed for Beidou satellite clock correction. + * + * <p>This is defined in BDS-SIS-ICD-B1I-3.0, section 5.2.4.9, 5.2.4.10. + */ + public static final class BeidouSatelliteClockModel implements Parcelable { + /** + * Time of the clock in seconds since Beidou epoch. + * + * <p>Corresponds to the 'Epoch' field within the 'SV/EPOCH/SV CLK' record of Beidou + * navigation message in RINEX 3.05 Table A14. + */ + private final long mTimeOfClockSeconds; + + /** SV clock bias in seconds. */ + private final double mAf0; + + /** SV clock drift in seconds per second. */ + private final double mAf1; + + /** SV clock drift in seconds per second squared. */ + private final double mAf2; + + /** Group delay differential 1 B1/B3 in seconds. */ + private final double mTgd1; + + /** Group delay differential 2 B2/B3 in seconds. */ + private final double mTgd2; + + /** + * Age of Data Clock. + * <p>This is defined in BDS-SIS-ICD-B1I-3.0 Section 5.2.4.8 Table 5-6. + */ + private final int mAodc; + + private BeidouSatelliteClockModel(Builder builder) { + Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); + Preconditions.checkArgumentInRange(builder.mAf0, -9.77e-3f, 9.77e-3f, "Af0"); + Preconditions.checkArgumentInRange(builder.mAf1, -1.87e-9f, 1.87e-9f, "Af1"); + Preconditions.checkArgumentInRange(builder.mAf2, -1.39e-17f, 1.39e-17f, "Af2"); + Preconditions.checkArgumentInRange(builder.mTgd1, -5.12e-8f, 5.12e-8f, "Tgd1"); + Preconditions.checkArgumentInRange(builder.mTgd2, -5.12e-8f, 5.12e-8f, "Tgd2"); + Preconditions.checkArgumentInRange(builder.mAodc, 0, 31, "Aodc"); + mTimeOfClockSeconds = builder.mTimeOfClockSeconds; + mAf0 = builder.mAf0; + mAf1 = builder.mAf1; + mAf2 = builder.mAf2; + mTgd1 = builder.mTgd1; + mTgd2 = builder.mTgd2; + mAodc = builder.mAodc; + } + + /** Returns the time of the clock in seconds since Beidou epoch. */ + @IntRange(from = 0) + public long getTimeOfClockSeconds() { + return mTimeOfClockSeconds; + } + + /** Returns the SV clock bias in seconds. */ + @FloatRange(from = -9.77e-3f, to = 9.77e-3f) + public double getAf0() { + return mAf0; + } + + /** Returns the SV clock drift in seconds per second. */ + @FloatRange(from = -1.87e-9f, to = 1.87e-9f) + public double getAf1() { + return mAf1; + } + + /** Returns the SV clock drift in seconds per second squared. */ + @FloatRange(from = -1.39e-17f, to = 1.39e-17f) + public double getAf2() { + return mAf2; + } + + /** Returns the group delay differential 1 B1/B3 in seconds. */ + @FloatRange(from = -5.12e-8f, to = 5.12e-8f) + public double getTgd1() { + return mTgd1; + } + + /** Returns the group delay differential 2 B2/B3 in seconds. */ + @FloatRange(from = -5.12e-8f, to = 5.12e-8f) + public double getTgd2() { + return mTgd2; + } + + /** Returns the age of data clock. */ + @IntRange(from = 0, to = 31) + public int getAodc() { + return mAodc; + } + + public static final @NonNull Creator<BeidouSatelliteClockModel> CREATOR = + new Creator<BeidouSatelliteClockModel>() { + @Override + @NonNull + public BeidouSatelliteClockModel createFromParcel(Parcel in) { + final BeidouSatelliteClockModel.Builder beidouSatelliteClockModel = + new Builder() + .setTimeOfClockSeconds(in.readLong()) + .setAf0(in.readDouble()) + .setAf1(in.readDouble()) + .setAf2(in.readDouble()) + .setTgd1(in.readDouble()) + .setTgd2(in.readDouble()) + .setAodc(in.readInt()); + return beidouSatelliteClockModel.build(); + } + + @Override + public BeidouSatelliteClockModel[] newArray(int size) { + return new BeidouSatelliteClockModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeLong(mTimeOfClockSeconds); + parcel.writeDouble(mAf0); + parcel.writeDouble(mAf1); + parcel.writeDouble(mAf2); + parcel.writeDouble(mTgd1); + parcel.writeDouble(mTgd2); + parcel.writeInt(mAodc); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("BeidouSatelliteClockModel["); + builder.append("timeOfClockSeonds = ").append(mTimeOfClockSeconds); + builder.append(", af0 = ").append(mAf0); + builder.append(", af1 = ").append(mAf1); + builder.append(", af2 = ").append(mAf2); + builder.append(", tgd1 = ").append(mTgd1); + builder.append(", tgd2 = ").append(mTgd2); + builder.append(", aodc = ").append(mAodc); + return builder.toString(); + } + + /** Builder for {@link BeidouSatelliteClockModel} */ + public static final class Builder { + private long mTimeOfClockSeconds; + private double mAf0; + private double mAf1; + private double mAf2; + private double mTgd1; + private double mTgd2; + private int mAodc; + + /** Sets the time of the clock in seconds since Beidou epoch. */ + @NonNull + public Builder setTimeOfClockSeconds(@IntRange(from = 0) long timeOfClockSeconds) { + mTimeOfClockSeconds = timeOfClockSeconds; + return this; + } + + /** Sets the SV clock bias in seconds. */ + @NonNull + public Builder setAf0(@FloatRange(from = -9.77e-3f, to = 9.77e-3f)double af0) { + mAf0 = af0; + return this; + } + + /** Sets the SV clock drift in seconds per second. */ + @NonNull + public Builder setAf1(@FloatRange(from = -1.87e-9f, to = 1.87e-9f) double af1) { + mAf1 = af1; + return this; + } + + /** Sets the SV clock drift in seconds per second squared. */ + @NonNull + public Builder setAf2(@FloatRange(from = -1.39e-17f, to = 1.39e-17f) double af2) { + mAf2 = af2; + return this; + } + + /** Sets the group delay differential 1 B1/B3 in seconds. */ + @NonNull + public Builder setTgd1(@FloatRange(from = -5.12e-8f, to = 5.12e-8f) double tgd1) { + mTgd1 = tgd1; + return this; + } + + /** Sets the group delay differential 2 B2/B3 in seconds. */ + @NonNull + public Builder setTgd2(@FloatRange(from = -5.12e-8f, to = 5.12e-8f) double tgd2) { + mTgd2 = tgd2; + return this; + } + + /** Sets the age of data clock. */ + @NonNull + public Builder setAodc(@IntRange(from = 0, to = 31) int aodc) { + mAodc = aodc; + return this; + } + + /** Builds a {@link BeidouSatelliteClockModel} instance as specified by this builder. */ + @NonNull + public BeidouSatelliteClockModel build() { + return new BeidouSatelliteClockModel(this); + } + } + } + + /** A class contains Beidou satellite health. */ + public static final class BeidouSatelliteHealth implements Parcelable { + /** + * The autonomous satellite health flag (SatH1) occupies 1 bit. + * + * <p>“0” means broadcasting satellite is good and “1” means not. + * + * <p>This is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.6. + */ + private final int mSatH1; + + /** + * SV accuracy in meters. + * + * <p>This is defined in the "BROADCAST ORBIT - 6" record of RINEX 3.05 + * Table A14, pp.78. + */ + private final double mSvAccur; + + private BeidouSatelliteHealth(Builder builder) { + // Allow SatH1 beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSatH1 >= 0); + Preconditions.checkArgumentInRange(builder.mSvAccur, 0.0f, 8192.0f, "SvAccur"); + mSatH1 = builder.mSatH1; + mSvAccur = builder.mSvAccur; + } + + /** Returns the autonomous satellite health flag (SatH1) */ + @IntRange(from = 0, to = 1) + public int getSatH1() { + return mSatH1; + } + + /** Returns the SV accuracy in meters. */ + @FloatRange(from = 0.0f, to = 8192.0f) + public double getSvAccur() { + return mSvAccur; + } + + public static final @NonNull Creator<BeidouSatelliteHealth> CREATOR = + new Creator<BeidouSatelliteHealth>() { + @Override + @NonNull + public BeidouSatelliteHealth createFromParcel(Parcel in) { + final BeidouSatelliteHealth.Builder beidouSatelliteHealth = + new Builder().setSatH1(in.readInt()).setSvAccur(in.readDouble()); + return beidouSatelliteHealth.build(); + } + + @Override + public BeidouSatelliteHealth[] newArray(int size) { + return new BeidouSatelliteHealth[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSatH1); + parcel.writeDouble(mSvAccur); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("BeidouSatelliteHealth["); + builder.append("satH1 = ").append(mSatH1); + builder.append(", svAccur = ").append(mSvAccur); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link BeidouSatelliteHealth} */ + public static final class Builder { + private int mSatH1; + private double mSvAccur; + + /** Sets the autonomous satellite health flag (SatH1) */ + @NonNull + public Builder setSatH1(int satH1) { + mSatH1 = satH1; + return this; + } + + /** Sets the SV accuracy in meters. */ + @NonNull + public Builder setSvAccur(double svAccur) { + mSvAccur = svAccur; + return this; + } + + /** Builds a {@link BeidouSatelliteHealth} instance as specified by this builder. */ + @NonNull + public BeidouSatelliteHealth build() { + return new BeidouSatelliteHealth(this); + } + } + } + + /** A class contains Beidou satellite ephemeris time. */ + public static final class BeidouSatelliteEphemerisTime implements Parcelable { + /** + * AODE Age of Data, Ephemeris. + * + * <p>This is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.11 Table 5-8. + */ + private final int mIode; + + /** Beidou week number without rollover */ + private final int mBeidouWeekNumber; + + /** + * Time of ephemeris in seconds. + * + * <p>This is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.12. + */ + private final int mToeSeconds; + + private BeidouSatelliteEphemerisTime(Builder builder) { + Preconditions.checkArgumentInRange(builder.mIode, 0, 31, "Iode"); + Preconditions.checkArgument(builder.mBeidouWeekNumber >= 0); + Preconditions.checkArgumentInRange(builder.mToeSeconds, 0, 604792, "ToeSeconds"); + mIode = builder.mIode; + mBeidouWeekNumber = builder.mBeidouWeekNumber; + mToeSeconds = builder.mToeSeconds; + } + + /** Returns the AODE Age of Data, Ephemeris. */ + @IntRange(from = 0, to = 31) + public int getIode() { + return mIode; + } + + /** Returns the Beidou week number without rollover . */ + @IntRange(from = 0) + public int getBeidouWeekNumber() { + return mBeidouWeekNumber; + } + + /** Returns the time of ephemeris in seconds. */ + @IntRange(from = 0, to = 604792) + public int getToeSeconds() { + return mToeSeconds; + } + + public static final @NonNull Creator<BeidouSatelliteEphemerisTime> CREATOR = + new Creator<BeidouSatelliteEphemerisTime>() { + @Override + @NonNull + public BeidouSatelliteEphemerisTime createFromParcel(Parcel in) { + final BeidouSatelliteEphemerisTime.Builder beidouSatelliteEphemerisTime = + new Builder() + .setIode(in.readInt()) + .setBeidouWeekNumber(in.readInt()) + .setToeSeconds(in.readInt()); + return beidouSatelliteEphemerisTime.build(); + } + + @Override + public BeidouSatelliteEphemerisTime[] newArray(int size) { + return new BeidouSatelliteEphemerisTime[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mIode); + parcel.writeInt(mBeidouWeekNumber); + parcel.writeInt(mToeSeconds); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("BeidouSatelliteEphemerisTime["); + builder.append("iode = ").append(mIode); + builder.append(", beidouWeekNumber = ").append(mBeidouWeekNumber); + builder.append(", toeSeconds = ").append(mToeSeconds); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link BeidouSatelliteEphemerisTime} */ + public static final class Builder { + private int mIode; + private int mBeidouWeekNumber; + private int mToeSeconds; + + /** Sets the AODE Age of Data, Ephemeris. */ + @NonNull + public Builder setIode(int iode) { + mIode = iode; + return this; + } + + /** Sets the Beidou week number without rollover */ + @NonNull + public Builder setBeidouWeekNumber( + @IntRange(from = 0) int beidouWeekNumber) { + mBeidouWeekNumber = beidouWeekNumber; + return this; + } + + /** Sets the time of ephemeris in seconds. */ + @NonNull + public Builder setToeSeconds(@IntRange(from = 0, to = 604792) int toeSeconds) { + mToeSeconds = toeSeconds; + return this; + } + + /** + * Builds a {@link BeidouSatelliteEphemerisTime} instance as specified by this builder. + */ + @NonNull + public BeidouSatelliteEphemerisTime build() { + return new BeidouSatelliteEphemerisTime(this); + } + } + } +} diff --git a/location/java/android/location/GalileoAssistance.java b/location/java/android/location/GalileoAssistance.java new file mode 100644 index 000000000000..07c5bab856db --- /dev/null +++ b/location/java/android/location/GalileoAssistance.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.location.GnssAssistance.GnssSatelliteCorrections; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains Galileo assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GalileoAssistance implements Parcelable { + + /** The Galileo almanac. */ + @Nullable private final GnssAlmanac mAlmanac; + + /** The Klobuchar ionospheric model. */ + @Nullable private final KlobucharIonosphericModel mIonosphericModel; + + /** The UTC model. */ + @Nullable private final UtcModel mUtcModel; + + /** The leap seconds model. */ + @Nullable private final LeapSecondsModel mLeapSecondsModel; + + /** The list of time models. */ + @NonNull private final List<TimeModel> mTimeModels; + + /** The list of Galileo ephemeris. */ + @NonNull private final List<GalileoSatelliteEphemeris> mSatelliteEphemeris; + + /** The list of real time integrity models. */ + @NonNull private final List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + + /** The list of Galileo satellite corrections. */ + @NonNull private final List<GnssSatelliteCorrections> mSatelliteCorrections; + + private GalileoAssistance(Builder builder) { + mAlmanac = builder.mAlmanac; + mIonosphericModel = builder.mIonosphericModel; + mUtcModel = builder.mUtcModel; + mLeapSecondsModel = builder.mLeapSecondsModel; + if (builder.mTimeModels != null) { + mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels)); + } else { + mTimeModels = new ArrayList<>(); + } + if (builder.mSatelliteEphemeris != null) { + mSatelliteEphemeris = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteEphemeris)); + } else { + mSatelliteEphemeris = new ArrayList<>(); + } + if (builder.mRealTimeIntegrityModels != null) { + mRealTimeIntegrityModels = + Collections.unmodifiableList(new ArrayList<>(builder.mRealTimeIntegrityModels)); + } else { + mRealTimeIntegrityModels = new ArrayList<>(); + } + if (builder.mSatelliteCorrections != null) { + mSatelliteCorrections = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteCorrections)); + } else { + mSatelliteCorrections = new ArrayList<>(); + } + } + + /** Returns the Galileo almanac. */ + @Nullable + public GnssAlmanac getAlmanac() { + return mAlmanac; + } + + /** Returns the Klobuchar ionospheric model. */ + @Nullable + public KlobucharIonosphericModel getIonosphericModel() { + return mIonosphericModel; + } + + /** Returns the UTC model. */ + @Nullable + public UtcModel getUtcModel() { + return mUtcModel; + } + + /** Returns the leap seconds model. */ + @Nullable + public LeapSecondsModel getLeapSecondsModel() { + return mLeapSecondsModel; + } + + /** Returns the list of time models. */ + @NonNull + public List<TimeModel> getTimeModels() { + return mTimeModels; + } + + /** Returns the list of Galileo ephemeris. */ + @NonNull + public List<GalileoSatelliteEphemeris> getSatelliteEphemeris() { + return mSatelliteEphemeris; + } + + /** Returns the list of real time integrity models. */ + @NonNull + public List<RealTimeIntegrityModel> getRealTimeIntegrityModels() { + return mRealTimeIntegrityModels; + } + + /** Returns the list of Galileo satellite corrections. */ + @NonNull + public List<GnssSatelliteCorrections> getSatelliteCorrections() { + return mSatelliteCorrections; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mAlmanac, flags); + dest.writeTypedObject(mIonosphericModel, flags); + dest.writeTypedObject(mUtcModel, flags); + dest.writeTypedObject(mLeapSecondsModel, flags); + dest.writeTypedList(mTimeModels); + dest.writeTypedList(mSatelliteEphemeris); + dest.writeTypedList(mRealTimeIntegrityModels); + dest.writeTypedList(mSatelliteCorrections); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GalileoAssistance["); + builder.append("almanac = ").append(mAlmanac); + builder.append(", ionosphericModel = ").append(mIonosphericModel); + builder.append(", utcModel = ").append(mUtcModel); + builder.append(", leapSecondsModel = ").append(mLeapSecondsModel); + builder.append(", timeModels = ").append(mTimeModels); + builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris); + builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels); + builder.append(", satelliteCorrections = ").append(mSatelliteCorrections); + builder.append("]"); + return builder.toString(); + } + + public static final @android.annotation.NonNull Creator<GalileoAssistance> CREATOR = + new Creator<GalileoAssistance>() { + @Override + public GalileoAssistance createFromParcel(Parcel in) { + return new GalileoAssistance.Builder() + .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR)) + .setIonosphericModel( + in.readTypedObject(KlobucharIonosphericModel.CREATOR)) + .setUtcModel(in.readTypedObject(UtcModel.CREATOR)) + .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR)) + .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR)) + .setSatelliteEphemeris( + in.createTypedArrayList(GalileoSatelliteEphemeris.CREATOR)) + .setRealTimeIntegrityModels( + in.createTypedArrayList(RealTimeIntegrityModel.CREATOR)) + .setSatelliteCorrections( + in.createTypedArrayList(GnssSatelliteCorrections.CREATOR)) + .build(); + } + + @Override + public GalileoAssistance[] newArray(int size) { + return new GalileoAssistance[size]; + } + }; + + /** Builder for {@link GalileoAssistance}. */ + public static final class Builder { + private GnssAlmanac mAlmanac; + private KlobucharIonosphericModel mIonosphericModel; + private UtcModel mUtcModel; + private LeapSecondsModel mLeapSecondsModel; + private List<TimeModel> mTimeModels; + private List<GalileoSatelliteEphemeris> mSatelliteEphemeris; + private List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + private List<GnssSatelliteCorrections> mSatelliteCorrections; + + /** Sets the Galileo almanac. */ + @NonNull + public Builder setAlmanac(@Nullable GnssAlmanac almanac) { + mAlmanac = almanac; + return this; + } + + /** Sets the Klobuchar ionospheric model. */ + @NonNull + public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) { + mIonosphericModel = ionosphericModel; + return this; + } + + /** Sets the UTC model. */ + @NonNull + public Builder setUtcModel(@Nullable UtcModel utcModel) { + mUtcModel = utcModel; + return this; + } + + /** Sets the leap seconds model. */ + @NonNull + public Builder setLeapSecondsModel(@Nullable LeapSecondsModel leapSecondsModel) { + mLeapSecondsModel = leapSecondsModel; + return this; + } + + /** Sets the list of time models. */ + @NonNull + public Builder setTimeModels( + @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) { + mTimeModels = timeModels; + return this; + } + + /** Sets the list of Galileo ephemeris. */ + @NonNull + public Builder setSatelliteEphemeris( + @Nullable @SuppressLint("NullableCollection") + List<GalileoSatelliteEphemeris> satelliteEphemeris) { + mSatelliteEphemeris = satelliteEphemeris; + return this; + } + + /** Sets the list of real time integrity models. */ + @NonNull + public Builder setRealTimeIntegrityModels( + @Nullable @SuppressLint("NullableCollection") + List<RealTimeIntegrityModel> realTimeIntegrityModels) { + mRealTimeIntegrityModels = realTimeIntegrityModels; + return this; + } + + /** Sets the list of Galileo satellite corrections. */ + @NonNull + public Builder setSatelliteCorrections( + @Nullable @SuppressLint("NullableCollection") + List<GnssSatelliteCorrections> satelliteCorrections) { + mSatelliteCorrections = satelliteCorrections; + return this; + } + + /** Builds the {@link GalileoAssistance}. */ + @NonNull + public GalileoAssistance build() { + return new GalileoAssistance(this); + } + } +} diff --git a/location/java/android/location/GalileoIonosphericModel.java b/location/java/android/location/GalileoIonosphericModel.java new file mode 100644 index 000000000000..6638be9af2b6 --- /dev/null +++ b/location/java/android/location/GalileoIonosphericModel.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Contains Galileo ionospheric model. + * + * <p>This is defined in Galileo-OS-SIS-ICD-v2.1, section 5.1.6. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GalileoIonosphericModel implements Parcelable { + /** Effective ionisation level 1st order parameter in sfu. */ + private final double mAi0; + + /** Effective ionisation level 2nd order parameter in sfu per degree. */ + private final double mAi1; + + /** Effective ionisation level 3nd order parameter in sfu per degree squared. */ + private final double mAi2; + + private GalileoIonosphericModel(Builder builder) { + Preconditions.checkArgumentInRange(builder.mAi0, 0.0f, 512.0f, "Ai0"); + Preconditions.checkArgumentInRange(builder.mAi1, -4.0f, 4.0f, "Ai1"); + Preconditions.checkArgumentInRange(builder.mAi2, -0.5f, 0.5f, "Ai2"); + mAi0 = builder.mAi0; + mAi1 = builder.mAi1; + mAi2 = builder.mAi2; + } + + /** Returns the effective ionisation level 1st order parameter in sfu. */ + @FloatRange(from = 0.0f, to = 512.0f) + public double getAi0() { + return mAi0; + } + + /** Returns the effective ionisation level 2nd order parameter in sfu per degree. */ + @FloatRange(from = -4.0f, to = 4.0f) + public double getAi1() { + return mAi1; + } + + /** Returns the effective ionisation level 3nd order parameter in sfu per degree squared. */ + @FloatRange(from = -0.5f, to = 0.5f) + public double getAi2() { + return mAi2; + } + + public static final @NonNull Parcelable.Creator<GalileoIonosphericModel> CREATOR = + new Parcelable.Creator<GalileoIonosphericModel>() { + @Override + public GalileoIonosphericModel createFromParcel(@NonNull Parcel source) { + return new GalileoIonosphericModel.Builder() + .setAi0(source.readDouble()) + .setAi1(source.readDouble()) + .setAi2(source.readDouble()) + .build(); + } + + @Override + public GalileoIonosphericModel[] newArray(int size) { + return new GalileoIonosphericModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeDouble(mAi0); + dest.writeDouble(mAi1); + dest.writeDouble(mAi2); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GalileoIonosphericModel["); + builder.append("ai0 = ").append(mAi0); + builder.append(", ai1 = ").append(mAi1); + builder.append(", ai2 = ").append(mAi2); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GalileoIonosphericModel}. */ + public static final class Builder { + private double mAi0; + private double mAi1; + private double mAi2; + + /** Sets the effective ionisation level 1st order parameter in sfu. */ + @NonNull + public Builder setAi0(@FloatRange(from = 0.0f, to = 512.0f) double ai0) { + mAi0 = ai0; + return this; + } + + /** Sets the effective ionisation level 2nd order parameter in sfu per degree. */ + @NonNull + public Builder setAi1(@FloatRange(from = -4.0f, to = 4.0f) double ai1) { + mAi1 = ai1; + return this; + } + + /** Sets the effective ionisation level 3nd order parameter in sfu per degree squared. */ + @NonNull + public Builder setAi2(@FloatRange(from = -0.5f, to = 0.5f) double ai2) { + mAi2 = ai2; + return this; + } + + /** Builds a {@link GalileoIonosphericModel}. */ + @NonNull + public GalileoIonosphericModel build() { + return new GalileoIonosphericModel(this); + } + } +} diff --git a/location/java/android/location/GalileoSatelliteEphemeris.java b/location/java/android/location/GalileoSatelliteEphemeris.java new file mode 100644 index 000000000000..7dd371176267 --- /dev/null +++ b/location/java/android/location/GalileoSatelliteEphemeris.java @@ -0,0 +1,660 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Contains ephemeris parameters specific to Galileo satellites. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GalileoSatelliteEphemeris implements Parcelable { + + /** Satellite code number. */ + private int mSatelliteCodeNumber; + + /** Array of satellite clock model. */ + @NonNull private final List<GalileoSatelliteClockModel> mSatelliteClockModels; + + /** Satellite orbit model. */ + @NonNull private final KeplerianOrbitModel mSatelliteOrbitModel; + + /** Satellite health. */ + @NonNull private final GalileoSvHealth mSatelliteHealth; + + /** Satellite ephemeris time. */ + @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; + + private GalileoSatelliteEphemeris(Builder builder) { + // Allow satelliteCodeNumber beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSatelliteCodeNumber >= 1); + Preconditions.checkNotNull( + builder.mSatelliteClockModels, "SatelliteClockModels cannot be null"); + Preconditions.checkNotNull( + builder.mSatelliteOrbitModel, "SatelliteOrbitModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteHealth, "SatelliteHealth cannot be null"); + Preconditions.checkNotNull( + builder.mSatelliteEphemerisTime, "SatelliteEphemerisTime cannot be null"); + mSatelliteCodeNumber = builder.mSatelliteCodeNumber; + final List<GalileoSatelliteClockModel> satelliteClockModels = builder.mSatelliteClockModels; + mSatelliteClockModels = Collections.unmodifiableList(new ArrayList<>(satelliteClockModels)); + mSatelliteOrbitModel = builder.mSatelliteOrbitModel; + mSatelliteHealth = builder.mSatelliteHealth; + mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; + } + + /** Returns the satellite code number. */ + @IntRange(from = 1, to = 36) + public int getSatelliteCodeNumber() { + return mSatelliteCodeNumber; + } + + /** Returns the list of satellite clock models. */ + @NonNull + public List<GalileoSatelliteClockModel> getSatelliteClockModels() { + return mSatelliteClockModels; + } + + /** Returns the satellite orbit model. */ + @NonNull + public KeplerianOrbitModel getSatelliteOrbitModel() { + return mSatelliteOrbitModel; + } + + /** Returns the satellite health. */ + @NonNull + public GalileoSvHealth getSatelliteHealth() { + return mSatelliteHealth; + } + + /** Returns the satellite ephemeris time. */ + @NonNull + public SatelliteEphemerisTime getSatelliteEphemerisTime() { + return mSatelliteEphemerisTime; + } + + public static final @NonNull Creator<GalileoSatelliteEphemeris> CREATOR = + new Creator<GalileoSatelliteEphemeris>() { + @Override + @NonNull + public GalileoSatelliteEphemeris createFromParcel(Parcel in) { + final GalileoSatelliteEphemeris.Builder galileoSatelliteEphemeris = + new Builder(); + galileoSatelliteEphemeris.setSatelliteCodeNumber(in.readInt()); + List<GalileoSatelliteClockModel> satelliteClockModels = new ArrayList<>(); + in.readTypedList(satelliteClockModels, GalileoSatelliteClockModel.CREATOR); + galileoSatelliteEphemeris.setSatelliteClockModels(satelliteClockModels); + galileoSatelliteEphemeris.setSatelliteOrbitModel( + in.readTypedObject(KeplerianOrbitModel.CREATOR)); + galileoSatelliteEphemeris.setSatelliteHealth( + in.readTypedObject(GalileoSvHealth.CREATOR)); + galileoSatelliteEphemeris.setSatelliteEphemerisTime( + in.readTypedObject(SatelliteEphemerisTime.CREATOR)); + return galileoSatelliteEphemeris.build(); + } + + @Override + public GalileoSatelliteEphemeris[] newArray(int size) { + return new GalileoSatelliteEphemeris[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSatelliteCodeNumber); + parcel.writeTypedList(mSatelliteClockModels, flags); + parcel.writeTypedObject(mSatelliteOrbitModel, flags); + parcel.writeTypedObject(mSatelliteHealth, flags); + parcel.writeTypedObject(mSatelliteEphemerisTime, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GalileoSatelliteEphemeris["); + builder.append("satelliteCodeNumber = ").append(mSatelliteCodeNumber); + builder.append(", satelliteClockModels = ").append(mSatelliteClockModels); + builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); + builder.append(", satelliteHealth = ").append(mSatelliteHealth); + builder.append(", satelliteEphemerisTime = ").append(mSatelliteEphemerisTime); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GalileoSatelliteEphemeris}. */ + public static final class Builder { + private int mSatelliteCodeNumber; + private List<GalileoSatelliteClockModel> mSatelliteClockModels; + private KeplerianOrbitModel mSatelliteOrbitModel; + private GalileoSvHealth mSatelliteHealth; + private SatelliteEphemerisTime mSatelliteEphemerisTime; + + /** Sets the satellite code number. */ + @NonNull + public Builder setSatelliteCodeNumber( + @IntRange(from = 1, to = 36) int satelliteCodeNumber) { + mSatelliteCodeNumber = satelliteCodeNumber; + return this; + } + + /** Sets the array of satellite clock model. */ + @NonNull + public Builder setSatelliteClockModels( + @NonNull List<GalileoSatelliteClockModel> satelliteClockModels) { + mSatelliteClockModels = satelliteClockModels; + return this; + } + + /** Sets the satellite orbit model. */ + @NonNull + public Builder setSatelliteOrbitModel(@NonNull KeplerianOrbitModel satelliteOrbitModel) { + mSatelliteOrbitModel = satelliteOrbitModel; + return this; + } + + /** Sets the satellite health. */ + @NonNull + public Builder setSatelliteHealth(@NonNull GalileoSvHealth satelliteHealth) { + mSatelliteHealth = satelliteHealth; + return this; + } + + /** Sets the satellite ephemeris time. */ + @NonNull + public Builder setSatelliteEphemerisTime( + @NonNull SatelliteEphemerisTime satelliteEphemerisTime) { + mSatelliteEphemerisTime = satelliteEphemerisTime; + return this; + } + + /** Builds a {@link GalileoSatelliteEphemeris} instance as specified by this builder. */ + @NonNull + public GalileoSatelliteEphemeris build() { + return new GalileoSatelliteEphemeris(this); + } + } + + /** + * A class contains the set of parameters needed for Galileo satellite health. + * + * <p>This is defined in Galileo-OS-SIS-ICD 5.1.9.3. + */ + public static final class GalileoSvHealth implements Parcelable { + /** E1-B data validity status. */ + private int mDataValidityStatusE1b; + + /** E1-B/C signal health status. */ + private int mSignalHealthStatusE1b; + + /** E5a data validity status. */ + private int mDataValidityStatusE5a; + + /** E5a signal health status. */ + private int mSignalHealthStatusE5a; + + /** E5b data validity status. */ + private int mDataValidityStatusE5b; + + /** E5b signal health status. */ + private int mSignalHealthStatusE5b; + + private GalileoSvHealth(Builder builder) { + Preconditions.checkArgumentInRange( + builder.mDataValidityStatusE1b, 0, 1, "DataValidityStatusE1b"); + Preconditions.checkArgumentInRange( + builder.mSignalHealthStatusE1b, 0, 3, "SignalHealthStatusE1b"); + Preconditions.checkArgumentInRange( + builder.mDataValidityStatusE5a, 0, 1, "DataValidityStatusE5a"); + Preconditions.checkArgumentInRange( + builder.mSignalHealthStatusE5a, 0, 3, "SignalHealthStatusE5a"); + Preconditions.checkArgumentInRange( + builder.mDataValidityStatusE5b, 0, 1, "DataValidityStatusE5b"); + Preconditions.checkArgumentInRange( + builder.mSignalHealthStatusE5b, 0, 3, "SignalHealthStatusE5b"); + mDataValidityStatusE1b = builder.mDataValidityStatusE1b; + mSignalHealthStatusE1b = builder.mSignalHealthStatusE1b; + mDataValidityStatusE5a = builder.mDataValidityStatusE5a; + mSignalHealthStatusE5a = builder.mSignalHealthStatusE5a; + mDataValidityStatusE5b = builder.mDataValidityStatusE5b; + mSignalHealthStatusE5b = builder.mSignalHealthStatusE5b; + } + + /** Returns the E1-B data validity status. */ + @IntRange(from = 0, to = 1) + public int getDataValidityStatusE1b() { + return mDataValidityStatusE1b; + } + + /** Returns the E1-B/C signal health status. */ + @IntRange(from = 0, to = 3) + public int getSignalHealthStatusE1b() { + return mSignalHealthStatusE1b; + } + + /** Returns the E5a data validity status. */ + @IntRange(from = 0, to = 1) + public int getDataValidityStatusE5a() { + return mDataValidityStatusE5a; + } + + /** Returns the E5a signal health status. */ + @IntRange(from = 0, to = 3) + public int getSignalHealthStatusE5a() { + return mSignalHealthStatusE5a; + } + + /** Returns the E5b data validity status. */ + @IntRange(from = 0, to = 1) + public int getDataValidityStatusE5b() { + return mDataValidityStatusE5b; + } + + /** Returns the E5b signal health status. */ + @IntRange(from = 0, to = 3) + public int getSignalHealthStatusE5b() { + return mSignalHealthStatusE5b; + } + + public static final @NonNull Creator<GalileoSvHealth> CREATOR = + new Creator<GalileoSvHealth>() { + @Override + @NonNull + public GalileoSvHealth createFromParcel(Parcel in) { + final GalileoSvHealth.Builder galileoSvHealth = new Builder(); + galileoSvHealth.setDataValidityStatusE1b(in.readInt()); + galileoSvHealth.setSignalHealthStatusE1b(in.readInt()); + galileoSvHealth.setDataValidityStatusE5a(in.readInt()); + galileoSvHealth.setSignalHealthStatusE5a(in.readInt()); + galileoSvHealth.setDataValidityStatusE5b(in.readInt()); + galileoSvHealth.setSignalHealthStatusE5b(in.readInt()); + return galileoSvHealth.build(); + } + + @Override + public GalileoSvHealth[] newArray(int size) { + return new GalileoSvHealth[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mDataValidityStatusE1b); + parcel.writeInt(mSignalHealthStatusE1b); + parcel.writeInt(mDataValidityStatusE5a); + parcel.writeInt(mSignalHealthStatusE5a); + parcel.writeInt(mDataValidityStatusE5b); + parcel.writeInt(mSignalHealthStatusE5b); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GalileoSvHealth["); + builder.append("dataValidityStatusE1b = ").append(mDataValidityStatusE1b); + builder.append(", signalHealthStatusE1b = ").append(mSignalHealthStatusE1b); + builder.append(", dataValidityStatusE5a = ").append(mDataValidityStatusE5a); + builder.append(", signalHealthStatusE5a = ").append(mSignalHealthStatusE5a); + builder.append(", dataValidityStatusE5b = ").append(mDataValidityStatusE5b); + builder.append(", signalHealthStatusE5b = ").append(mSignalHealthStatusE5b); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GalileoSvHealth}. */ + public static final class Builder { + private int mDataValidityStatusE1b; + private int mSignalHealthStatusE1b; + private int mDataValidityStatusE5a; + private int mSignalHealthStatusE5a; + private int mDataValidityStatusE5b; + private int mSignalHealthStatusE5b; + + /** Sets the E1-B data validity status. */ + @NonNull + public Builder setDataValidityStatusE1b( + @IntRange(from = 0, to = 1) int dataValidityStatusE1b) { + mDataValidityStatusE1b = dataValidityStatusE1b; + return this; + } + + /** Sets the E1-B/C signal health status. */ + @NonNull + public Builder setSignalHealthStatusE1b( + @IntRange(from = 0, to = 3) int signalHealthStatusE1b) { + mSignalHealthStatusE1b = signalHealthStatusE1b; + return this; + } + + /** Sets the E5a data validity status. */ + @NonNull + public Builder setDataValidityStatusE5a( + @IntRange(from = 0, to = 1) int dataValidityStatusE5a) { + mDataValidityStatusE5a = dataValidityStatusE5a; + return this; + } + + /** Sets the E5a signal health status. */ + @NonNull + public Builder setSignalHealthStatusE5a( + @IntRange(from = 0, to = 3) int signalHealthStatusE5a) { + mSignalHealthStatusE5a = signalHealthStatusE5a; + return this; + } + + /** Sets the E5b data validity status. */ + @NonNull + public Builder setDataValidityStatusE5b( + @IntRange(from = 0, to = 1) int dataValidityStatusE5b) { + mDataValidityStatusE5b = dataValidityStatusE5b; + return this; + } + + /** Sets the E5b signal health status. */ + @NonNull + public Builder setSignalHealthStatusE5b( + @IntRange(from = 0, to = 3) int signalHealthStatusE5b) { + mSignalHealthStatusE5b = signalHealthStatusE5b; + return this; + } + + /** Builds a {@link GalileoSvHealth}. */ + @NonNull + public GalileoSvHealth build() { + return new GalileoSvHealth(this); + } + } + } + + /** + * A class contains the set of parameters needed for Galileo satellite clock correction. + * + * <p>This is defined in Galileo-OS-SIS-ICD 5.1.3. + */ + public static final class GalileoSatelliteClockModel implements Parcelable { + + /** + * The type of the satellite clock. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNDEFINED, TYPE_FNAV, TYPE_INAV}) + public @interface SatelliteClockType {} + + /** + * The following enumerations must be in sync with the values declared in + * GalileoSatelliteEphemeris.aidl. + */ + + /** The type of the satellite clock is unknown. */ + public static final int TYPE_UNDEFINED = 0; + + /** The type of the satellite clock is FNAV. */ + public static final int TYPE_FNAV = 1; + + /** The type of the satellite clock is INAV. */ + public static final int TYPE_INAV = 2; + + /** + * Time of the clock in seconds since Galileo epoch. + * + * <p>Corresponds to the 'Epoch' field within the 'SV/EPOCH/SV CLK' record of Galileo + * navigation message in RINEX 3.05 Table A8. + */ + private final long mTimeOfClockSeconds; + + /** SV clock bias correction coefficient in seconds. */ + private double mAf0; + + /** SV clock drift correction coefficient in seconds per second. */ + private double mAf1; + + /** SV clock drift rate correction coefficient in seconds per second squared. */ + private double mAf2; + + /** + * Broadcast group delay in seconds. + * + * <p>This is defined in Galileo-OS-SIS-ICD 5.1.5. + */ + private double mBgdSeconds; + + /** + * Signal in space accuracy in meters. + * + * <p>This is defined in Galileo-OS-SIS-ICD 5.1.12. + */ + private double mSisaMeters; + + /** Type of satellite clock . */ + private final @SatelliteClockType int mSatelliteClockType; + + private GalileoSatelliteClockModel(Builder builder) { + Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); + Preconditions.checkArgumentInRange(builder.mAf0, -0.0625f, 0.0625f, "AF0"); + Preconditions.checkArgumentInRange(builder.mAf1, -1.5e-8f, 1.5e-8f, "AF1"); + Preconditions.checkArgumentInRange(builder.mAf2, -5.56e-17f, 5.56e-17f, "AF2"); + Preconditions.checkArgumentInRange( + builder.mBgdSeconds, -1.2e-7f, 1.2e-7f, "BgdSeconds"); + Preconditions.checkArgument(builder.mSisaMeters >= 0.0f); + Preconditions.checkArgumentInRange( + builder.mSatelliteClockType, TYPE_UNDEFINED, TYPE_INAV, "SatelliteClockType"); + mTimeOfClockSeconds = builder.mTimeOfClockSeconds; + mAf0 = builder.mAf0; + mAf1 = builder.mAf1; + mAf2 = builder.mAf2; + mBgdSeconds = builder.mBgdSeconds; + mSisaMeters = builder.mSisaMeters; + mSatelliteClockType = builder.mSatelliteClockType; + } + + /** Returns the time of the clock in seconds since Galileo epoch. */ + @IntRange(from = 0) + public long getTimeOfClockSeconds() { + return mTimeOfClockSeconds; + } + + /** Returns the SV clock bias correction coefficient in seconds. */ + @FloatRange(from = -0.0625f, to = 0.0625f) + public double getAf0() { + return mAf0; + } + + /** Returns the SV clock drift correction coefficient in seconds per second. */ + @FloatRange(from = -1.5e-8f, to = 1.5e-8f) + public double getAf1() { + return mAf1; + } + + /** Returns the SV clock drift rate correction coefficient in seconds per second squared. */ + @FloatRange(from = -5.56e-17f, to = 5.56e-17f) + public double getAf2() { + return mAf2; + } + + /** Returns the broadcast group delay in seconds. */ + @FloatRange(from = -1.2e-7f, to = 1.2e-7f) + public double getBgdSeconds() { + return mBgdSeconds; + } + + /** Returns the signal in space accuracy in meters. */ + @FloatRange(from = 0.0f) + public double getSisaMeters() { + return mSisaMeters; + } + + /** Returns the type of satellite clock. */ + public @SatelliteClockType int getSatelliteClockType() { + return mSatelliteClockType; + } + + public static final @NonNull Creator<GalileoSatelliteClockModel> CREATOR = + new Creator<GalileoSatelliteClockModel>() { + @Override + @NonNull + public GalileoSatelliteClockModel createFromParcel(Parcel in) { + final GalileoSatelliteClockModel.Builder galileoSatelliteClockModel = + new Builder(); + galileoSatelliteClockModel.setTimeOfClockSeconds(in.readLong()); + galileoSatelliteClockModel.setAf0(in.readDouble()); + galileoSatelliteClockModel.setAf1(in.readDouble()); + galileoSatelliteClockModel.setAf2(in.readDouble()); + galileoSatelliteClockModel.setBgdSeconds(in.readDouble()); + galileoSatelliteClockModel.setSisaMeters(in.readDouble()); + galileoSatelliteClockModel.setSatelliteClockType(in.readInt()); + return galileoSatelliteClockModel.build(); + } + + @Override + public GalileoSatelliteClockModel[] newArray(int size) { + return new GalileoSatelliteClockModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeLong(mTimeOfClockSeconds); + parcel.writeDouble(mAf0); + parcel.writeDouble(mAf1); + parcel.writeDouble(mAf2); + parcel.writeDouble(mBgdSeconds); + parcel.writeDouble(mSisaMeters); + parcel.writeInt(mSatelliteClockType); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GalileoSatelliteClockModel["); + builder.append("timeOfClockSeconds = ").append(mTimeOfClockSeconds); + builder.append(", af0 = ").append(mAf0); + builder.append(", af1 = ").append(mAf1); + builder.append(", af2 = ").append(mAf2); + builder.append(", bgdSeconds = ").append(mBgdSeconds); + builder.append(", sisaMeters = ").append(mSisaMeters); + builder.append(", satelliteClockType = ").append(mSatelliteClockType); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GalileoSatelliteClockModel}. */ + public static final class Builder { + private long mTimeOfClockSeconds; + private double mAf0; + private double mAf1; + private double mAf2; + private double mBgdSeconds; + private double mSisaMeters; + private @SatelliteClockType int mSatelliteClockType; + + /** Sets the time of the clock in seconds since Galileo epoch. */ + @NonNull + public Builder setTimeOfClockSeconds(@IntRange(from = 0) long timeOfClockSeconds) { + mTimeOfClockSeconds = timeOfClockSeconds; + return this; + } + + /** Sets the SV clock bias correction coefficient in seconds. */ + @NonNull + public Builder setAf0(@FloatRange(from = -0.0625f, to = 0.0625f) double af0) { + mAf0 = af0; + return this; + } + + /** Sets the SV clock drift correction coefficient in seconds per second. */ + @NonNull + public Builder setAf1(@FloatRange(from = -1.5e-8f, to = 1.5e-8f) double af1) { + mAf1 = af1; + return this; + } + + /** + * Sets the SV clock drift rate correction coefficient in seconds per second squared. + */ + @NonNull + public Builder setAf2(@FloatRange(from = -5.56e-17f, to = 5.56e-17f) double af2) { + mAf2 = af2; + return this; + } + + /** Sets the broadcast group delay in seconds. */ + @NonNull + public Builder setBgdSeconds( + @FloatRange(from = -1.2e-7f, to = 1.2e-7f) double bgdSeconds) { + mBgdSeconds = bgdSeconds; + return this; + } + + /** Sets the signal in space accuracy in meters. */ + @NonNull + public Builder setSisaMeters(@FloatRange(from = 0.0f) double sisaMeters) { + mSisaMeters = sisaMeters; + return this; + } + + /** Sets the type of satellite clock. */ + @NonNull + public Builder setSatelliteClockType(@SatelliteClockType int satelliteClockType) { + mSatelliteClockType = satelliteClockType; + return this; + } + + /** + * Builds a {@link GalileoSatelliteClockModel} instance as specified by this builder. + */ + @NonNull + public GalileoSatelliteClockModel build() { + return new GalileoSatelliteClockModel(this); + } + } + } +} diff --git a/location/java/android/location/GlonassAlmanac.java b/location/java/android/location/GlonassAlmanac.java new file mode 100644 index 000000000000..861dc5d257c4 --- /dev/null +++ b/location/java/android/location/GlonassAlmanac.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains Glonass almanac data. + * + * <p>This is defined in Glonass ICD v5.1 section 4.5. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GlonassAlmanac implements Parcelable { + + /** Almanac issue date in milliseconds (UTC) */ + private final long mIssueDateMillis; + + /** List of GlonassSatelliteAlmanacs. */ + @NonNull private final List<GlonassSatelliteAlmanac> mSatelliteAlmanacs; + + /** + * Constructor for GlonassAlmanac. + * + * @param issueDateMillis The almanac issue date in milliseconds (UTC). + * @param satelliteAlmanacs The list of GlonassSatelliteAlmanac. + */ + public GlonassAlmanac( + @IntRange(from = 0) long issueDateMillis, + @NonNull List<GlonassSatelliteAlmanac> satelliteAlmanacs) { + Preconditions.checkArgument(issueDateMillis >= 0); + Preconditions.checkNotNull(satelliteAlmanacs, "satelliteAlmanacs cannot be null"); + mIssueDateMillis = issueDateMillis; + mSatelliteAlmanacs = Collections.unmodifiableList(new ArrayList<>(satelliteAlmanacs)); + } + + /** Returns the almanac issue date in milliseconds (UTC). */ + @IntRange(from = 0) + public long getIssueDateMillis() { + return mIssueDateMillis; + } + + /** Returns the list of GlonassSatelliteAlmanacs. */ + @NonNull + public List<GlonassSatelliteAlmanac> getSatelliteAlmanacs() { + return mSatelliteAlmanacs; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mIssueDateMillis); + dest.writeTypedList(mSatelliteAlmanacs); + } + + public static final @NonNull Parcelable.Creator<GlonassAlmanac> CREATOR = + new Parcelable.Creator<GlonassAlmanac>() { + @Override + public GlonassAlmanac createFromParcel(@NonNull Parcel in) { + long issueDateMillis = in.readLong(); + List<GlonassSatelliteAlmanac> satelliteAlmanacs = new ArrayList<>(); + in.readTypedList(satelliteAlmanacs, GlonassSatelliteAlmanac.CREATOR); + return new GlonassAlmanac(issueDateMillis, satelliteAlmanacs); + } + + @Override + public GlonassAlmanac[] newArray(int size) { + return new GlonassAlmanac[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassAlmanac["); + builder.append("issueDateMillis = ").append(mIssueDateMillis); + builder.append(", satelliteAlmanacs = ").append(mSatelliteAlmanacs); + builder.append("]"); + return builder.toString(); + } + + /** + * A class contains Glonass satellite almanac data. + * + * <p>This is defined in Glonass ICD v5.1 section 4.5. + */ + public static final class GlonassSatelliteAlmanac implements Parcelable { + /** Slot number. */ + private final int mSlotNumber; + + /** Satellite health information (0=healthy, 1=unhealthy). */ + private final int mSvHealth; + + /** Frequency channel number. */ + private final int mFreqChannel; + + /** Coarse value of satellite time correction to GLONASS time in seconds. */ + private final double mTau; + + /** Time of first ascending node passage of satellite in seconds. */ + private final double mTLambda; + + /** Longitude of the first ascending node in semi-circles. */ + private final double mLambda; + + /** Correction to the mean value of inclination in semi-circles. */ + private final double mDeltaI; + + /** Correction to the mean value of the draconian period in seconds per orbital period */ + private final double mDeltaT; + + /** Rate of change of draconian period in seconds per orbital period squared. */ + private final double mDeltaTDot; + + /** Eccentricity. */ + private final double mEccentricity; + + /** Argument of perigee in radians. */ + private final double mOmega; + + private GlonassSatelliteAlmanac(Builder builder) { + // Allow slotNumber beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSlotNumber >= 1); + // Allow svHealth beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvHealth >= 0); + Preconditions.checkArgumentInRange(builder.mFreqChannel, 0, 31, "FreqChannel"); + Preconditions.checkArgumentInRange(builder.mTau, -1.9e-3f, 1.9e-3f, "Tau"); + Preconditions.checkArgumentInRange(builder.mTLambda, 0.0f, 44100.0f, "TLambda"); + Preconditions.checkArgumentInRange(builder.mLambda, -1.0f, 1.0f, "Lambda"); + Preconditions.checkArgumentInRange(builder.mDeltaI, -0.067f, 0.067f, "DeltaI"); + Preconditions.checkArgumentInRange(builder.mDeltaT, -3600.0f, 3600.0f, "DeltaT"); + Preconditions.checkArgumentInRange(builder.mDeltaTDot, -0.004f, 0.004f, "DeltaTDot"); + Preconditions.checkArgumentInRange(builder.mEccentricity, 0.0f, 0.03f, "Eccentricity"); + Preconditions.checkArgumentInRange(builder.mOmega, -1.0f, 1.0f, "Omega"); + mSlotNumber = builder.mSlotNumber; + mSvHealth = builder.mSvHealth; + mFreqChannel = builder.mFreqChannel; + mTau = builder.mTau; + mTLambda = builder.mTLambda; + mLambda = builder.mLambda; + mDeltaI = builder.mDeltaI; + mDeltaT = builder.mDeltaT; + mDeltaTDot = builder.mDeltaTDot; + mEccentricity = builder.mEccentricity; + mOmega = builder.mOmega; + } + + /** Returns the slot number. */ + @IntRange(from = 1, to = 25) + public int getSlotNumber() { + return mSlotNumber; + } + + /** Returns the Satellite health information (0=healthy, 1=unhealthy). */ + @IntRange(from = 0, to = 1) + public int getSvHealth() { + return mSvHealth; + } + + /** Returns the frequency channel number. */ + @IntRange(from = 0, to = 31) + public int getFreqChannel() { + return mFreqChannel; + } + + /** Returns the coarse value of satellite time correction to GLONASS time in seconds. */ + @FloatRange(from = -1.9e-3f, to = 1.9e-3f) + public double getTau() { + return mTau; + } + + /** Returns the time of first ascending node passage of satellite in seconds. */ + @FloatRange(from = 0.0f, to = 44100.0f) + public double getTLambda() { + return mTLambda; + } + + /** Returns the longitude of the first ascending node in semi-circles. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getLambda() { + return mLambda; + } + + /** Returns the correction to the mean value of inclination in semi-circles. */ + @FloatRange(from = -0.067f, to = 0.067f) + public double getDeltaI() { + return mDeltaI; + } + + /** + * Returns the correction to the mean value of the draconian period in seconds per orbital + * period + */ + @FloatRange(from = -3600.0f, to = 3600.0f) + public double getDeltaT() { + return mDeltaT; + } + + /** Returns the rate of change of draconian period in seconds per orbital period squared. */ + @FloatRange(from = -0.004f, to = 0.004f) + public double getDeltaTDot() { + return mDeltaTDot; + } + + /** Returns the eccentricity. */ + @FloatRange(from = 0.0f, to = 0.03f) + public double getEccentricity() { + return mEccentricity; + } + + /** Returns the argument of perigee in radians. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getOmega() { + return mOmega; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSlotNumber); + dest.writeInt(mSvHealth); + dest.writeInt(mFreqChannel); + dest.writeDouble(mTau); + dest.writeDouble(mTLambda); + dest.writeDouble(mLambda); + dest.writeDouble(mDeltaI); + dest.writeDouble(mDeltaT); + dest.writeDouble(mDeltaTDot); + dest.writeDouble(mEccentricity); + dest.writeDouble(mOmega); + } + + public static final @NonNull Parcelable.Creator<GlonassSatelliteAlmanac> CREATOR = + new Parcelable.Creator<GlonassSatelliteAlmanac>() { + @Override + public GlonassSatelliteAlmanac createFromParcel(@NonNull Parcel source) { + return new GlonassSatelliteAlmanac.Builder() + .setSlotNumber(source.readInt()) + .setSvHealth(source.readInt()) + .setFreqChannel(source.readInt()) + .setTau(source.readDouble()) + .setTLambda(source.readDouble()) + .setLambda(source.readDouble()) + .setDeltaI(source.readDouble()) + .setDeltaT(source.readDouble()) + .setDeltaTDot(source.readDouble()) + .setEccentricity(source.readDouble()) + .setOmega(source.readDouble()) + .build(); + } + + @Override + public GlonassSatelliteAlmanac[] newArray(int size) { + return new GlonassSatelliteAlmanac[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassSatelliteAlmanac["); + builder.append("slotNumber = ").append(mSlotNumber); + builder.append(", svHealth = ").append(mSvHealth); + builder.append(", freqChannel = ").append(mFreqChannel); + builder.append(", tau = ").append(mTau); + builder.append(", tLambda = ").append(mTLambda); + builder.append(", lambda = ").append(mLambda); + builder.append(", deltaI = ").append(mDeltaI); + builder.append(", deltaT = ").append(mDeltaT); + builder.append(", deltaTDot = ").append(mDeltaTDot); + builder.append(", eccentricity = ").append(mEccentricity); + builder.append(", omega = ").append(mOmega); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GlonassSatelliteAlmanac}. */ + public static final class Builder { + private int mSlotNumber; + private int mSvHealth; + private int mFreqChannel; + private double mTau; + private double mTLambda; + private double mLambda; + private double mDeltaI; + private double mDeltaT; + private double mDeltaTDot; + private double mEccentricity; + private double mOmega; + + /** Sets the slot number. */ + @NonNull + public Builder setSlotNumber(@IntRange(from = 1, to = 25) int slotNumber) { + mSlotNumber = slotNumber; + return this; + } + + /** Sets the Satellite health information (0=healthy, 1=unhealthy). */ + @NonNull + public Builder setSvHealth(@IntRange(from = 0, to = 1) int svHealth) { + mSvHealth = svHealth; + return this; + } + + /** Sets the frequency channel number. */ + @NonNull + public Builder setFreqChannel(@IntRange(from = 0, to = 31) int freqChannel) { + mFreqChannel = freqChannel; + return this; + } + + /** Sets the coarse value of satellite time correction to GLONASS time in seconds. */ + @NonNull + public Builder setTau(@FloatRange(from = -1.9e-3f, to = 1.9e-3f) double tau) { + mTau = tau; + return this; + } + + /** Sets the time of first ascending node passage of satellite in seconds. */ + @NonNull + public Builder setTLambda(@FloatRange(from = 0.0f, to = 44100.0f) double tLambda) { + mTLambda = tLambda; + return this; + } + + /** Sets the longitude of the first ascending node in semi-circles. */ + @NonNull + public Builder setLambda(@FloatRange(from = -1.0f, to = 1.0f) double lambda) { + mLambda = lambda; + return this; + } + + /** Sets the correction to the mean value of inclination in semi-circles. */ + @NonNull + public Builder setDeltaI(@FloatRange(from = -0.067f, to = 0.067f) double deltaI) { + mDeltaI = deltaI; + return this; + } + + /** + * Sets the correction to the mean value of the draconian period in seconds per orbital + * period. + */ + @NonNull + public Builder setDeltaT(@FloatRange(from = -3600.0f, to = 3600.0f) double deltaT) { + mDeltaT = deltaT; + return this; + } + + /** + * Sets the rate of change of draconian period in seconds per orbital period squared. + */ + @NonNull + public Builder setDeltaTDot(@FloatRange(from = -0.004f, to = 0.004f) double deltaTDot) { + mDeltaTDot = deltaTDot; + return this; + } + + /** Sets the eccentricity. */ + @NonNull + public Builder setEccentricity( + @FloatRange(from = 0.0f, to = 0.03f) double eccentricity) { + mEccentricity = eccentricity; + return this; + } + + /** Sets the argument of perigee in radians. */ + @NonNull + public Builder setOmega(@FloatRange(from = -1.0f, to = 1.0f) double omega) { + mOmega = omega; + return this; + } + + /** Builds a {@link GlonassSatelliteAlmanac}. */ + @NonNull + public GlonassSatelliteAlmanac build() { + return new GlonassSatelliteAlmanac(this); + } + } + } +} diff --git a/location/java/android/location/GlonassAssistance.java b/location/java/android/location/GlonassAssistance.java new file mode 100644 index 000000000000..cc0820197d8d --- /dev/null +++ b/location/java/android/location/GlonassAssistance.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.location.GnssAssistance.GnssSatelliteCorrections; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains Glonass assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GlonassAssistance implements Parcelable { + + /** The Glonass almanac. */ + @Nullable private final GlonassAlmanac mAlmanac; + + /** The UTC model. */ + @Nullable private final UtcModel mUtcModel; + + /** The list of time models. */ + @NonNull private final List<TimeModel> mTimeModels; + + /** The list of Glonass ephemeris. */ + @NonNull private final List<GlonassSatelliteEphemeris> mSatelliteEphemeris; + + /** The list of Glonass satellite corrections. */ + @NonNull private final List<GnssSatelliteCorrections> mSatelliteCorrections; + + private GlonassAssistance(Builder builder) { + mAlmanac = builder.mAlmanac; + mUtcModel = builder.mUtcModel; + if (builder.mTimeModels != null) { + mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels)); + } else { + mTimeModels = new ArrayList<>(); + } + if (builder.mSatelliteEphemeris != null) { + mSatelliteEphemeris = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteEphemeris)); + } else { + mSatelliteEphemeris = new ArrayList<>(); + } + if (builder.mSatelliteCorrections != null) { + mSatelliteCorrections = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteCorrections)); + } else { + mSatelliteCorrections = new ArrayList<>(); + } + } + + /** Returns the Glonass almanac. */ + @Nullable + public GlonassAlmanac getAlmanac() { + return mAlmanac; + } + + /** Returns the UTC model. */ + @Nullable + public UtcModel getUtcModel() { + return mUtcModel; + } + + /** Returns the list of time models. */ + @NonNull + public List<TimeModel> getTimeModels() { + return mTimeModels; + } + + /** Returns the list of Glonass satellite ephemeris. */ + @NonNull + public List<GlonassSatelliteEphemeris> getSatelliteEphemeris() { + return mSatelliteEphemeris; + } + + /** Returns the list of Glonass satellite corrections. */ + @NonNull + public List<GnssSatelliteCorrections> getSatelliteCorrections() { + return mSatelliteCorrections; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mAlmanac, flags); + dest.writeTypedObject(mUtcModel, flags); + dest.writeTypedList(mTimeModels); + dest.writeTypedList(mSatelliteEphemeris); + dest.writeTypedList(mSatelliteCorrections); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassAssistance["); + builder.append("almanac = ").append(mAlmanac); + builder.append(", utcModel = ").append(mUtcModel); + builder.append(", timeModels = ").append(mTimeModels); + builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris); + builder.append(", satelliteCorrections = ").append(mSatelliteCorrections); + builder.append("]"); + return builder.toString(); + } + + public static final @NonNull Creator<GlonassAssistance> CREATOR = + new Creator<GlonassAssistance>() { + @Override + public GlonassAssistance createFromParcel(Parcel in) { + return new GlonassAssistance.Builder() + .setAlmanac(in.readTypedObject(GlonassAlmanac.CREATOR)) + .setUtcModel(in.readTypedObject(UtcModel.CREATOR)) + .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR)) + .setSatelliteEphemeris( + in.createTypedArrayList(GlonassSatelliteEphemeris.CREATOR)) + .setSatelliteCorrections( + in.createTypedArrayList(GnssSatelliteCorrections.CREATOR)) + .build(); + } + + @Override + public GlonassAssistance[] newArray(int size) { + return new GlonassAssistance[size]; + } + }; + + /** Builder for {@link GlonassAssistance}. */ + public static final class Builder { + private GlonassAlmanac mAlmanac; + private UtcModel mUtcModel; + private List<TimeModel> mTimeModels; + private List<GlonassSatelliteEphemeris> mSatelliteEphemeris; + private List<GnssSatelliteCorrections> mSatelliteCorrections; + + /** Sets the Glonass almanac. */ + @NonNull + public Builder setAlmanac( + @Nullable @SuppressLint("NullableCollection") GlonassAlmanac almanac) { + mAlmanac = almanac; + return this; + } + + /** Sets the UTC model. */ + @NonNull + public Builder setUtcModel( + @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) { + mUtcModel = utcModel; + return this; + } + + /** Sets the list of time models. */ + @NonNull + public Builder setTimeModels( + @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) { + mTimeModels = timeModels; + return this; + } + + /** Sets the list of Glonass satellite ephemeris. */ + @NonNull + public Builder setSatelliteEphemeris( + @Nullable @SuppressLint("NullableCollection") + List<GlonassSatelliteEphemeris> satelliteEphemeris) { + mSatelliteEphemeris = satelliteEphemeris; + return this; + } + + /** Sets the list of Glonass satellite corrections. */ + @NonNull + public Builder setSatelliteCorrections( + @Nullable @SuppressLint("NullableCollection") + List<GnssSatelliteCorrections> satelliteCorrections) { + mSatelliteCorrections = satelliteCorrections; + return this; + } + + /** Builds the {@link GlonassAssistance}. */ + @NonNull + public GlonassAssistance build() { + return new GlonassAssistance(this); + } + } +} diff --git a/location/java/android/location/GlonassSatelliteEphemeris.java b/location/java/android/location/GlonassSatelliteEphemeris.java new file mode 100644 index 000000000000..77a6ebb50cfb --- /dev/null +++ b/location/java/android/location/GlonassSatelliteEphemeris.java @@ -0,0 +1,623 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains ephemeris parameters specific to Glonass satellites. + * + * <p>This is defined in RINEX 3.05 APPENDIX 10 and Glonass ICD v5.1 section 4.4. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GlonassSatelliteEphemeris implements Parcelable { + + /** L1/Satellite system (R), satellite number (slot number in sat. constellation). */ + private final int mSlotNumber; + + /** Health state (0=healthy, 1=unhealthy). */ + private final int mHealthState; + + /** Message frame time in seconds of the UTC week (tk+nd*86400). */ + private final double mFrameTimeSeconds; + + /** Age of current information in days (E). */ + private final int mAgeInDays; + + /** Satellite clock model. */ + @NonNull private final GlonassSatelliteClockModel mSatelliteClockModel; + + /** Satellite orbit model. */ + @NonNull private final GlonassSatelliteOrbitModel mSatelliteOrbitModel; + + private GlonassSatelliteEphemeris(Builder builder) { + // Allow SlotNumber beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSlotNumber >= 1); + // Allow HealthState beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mHealthState >= 0); + Preconditions.checkArgument(builder.mFrameTimeSeconds >= 0.0f); + Preconditions.checkArgumentInRange(builder.mAgeInDays, 0, 31, "AgeInDays"); + Preconditions.checkNotNull( + builder.mSatelliteClockModel, "SatelliteClockModel cannot be null"); + Preconditions.checkNotNull( + builder.mSatelliteOrbitModel, "SatelliteOrbitModel cannot be null"); + mSlotNumber = builder.mSlotNumber; + mHealthState = builder.mHealthState; + mFrameTimeSeconds = builder.mFrameTimeSeconds; + mAgeInDays = builder.mAgeInDays; + mSatelliteClockModel = builder.mSatelliteClockModel; + mSatelliteOrbitModel = builder.mSatelliteOrbitModel; + } + + /** + * Returns the L1/Satellite system (R), satellite number (slot number in sat. constellation). + */ + @IntRange(from = 1, to = 25) + public int getSlotNumber() { + return mSlotNumber; + } + + /** Returns the health state (0=healthy, 1=unhealthy). */ + @IntRange(from = 0, to = 1) + public int getHealthState() { + return mHealthState; + } + + /** Returns the message frame time in seconds of the UTC week (tk+nd*86400). */ + @FloatRange(from = 0.0f) + public double getFrameTimeSeconds() { + return mFrameTimeSeconds; + } + + /** Returns the age of current information in days (E). */ + @IntRange(from = 0, to = 31) + public int getAgeInDays() { + return mAgeInDays; + } + + /** Returns the satellite clock model. */ + @NonNull + public GlonassSatelliteClockModel getSatelliteClockModel() { + return mSatelliteClockModel; + } + + /** Returns the satellite orbit model. */ + @NonNull + public GlonassSatelliteOrbitModel getSatelliteOrbitModel() { + return mSatelliteOrbitModel; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSlotNumber); + dest.writeInt(mHealthState); + dest.writeDouble(mFrameTimeSeconds); + dest.writeInt(mAgeInDays); + dest.writeTypedObject(mSatelliteClockModel, flags); + dest.writeTypedObject(mSatelliteOrbitModel, flags); + } + + public static final @NonNull Parcelable.Creator<GlonassSatelliteEphemeris> CREATOR = + new Parcelable.Creator<GlonassSatelliteEphemeris>() { + @Override + public GlonassSatelliteEphemeris createFromParcel(@NonNull Parcel source) { + return new GlonassSatelliteEphemeris.Builder() + .setSlotNumber(source.readInt()) + .setHealthState(source.readInt()) + .setFrameTimeSeconds(source.readDouble()) + .setAgeInDays(source.readInt()) + .setSatelliteClockModel( + source.readTypedObject(GlonassSatelliteClockModel.CREATOR)) + .setSatelliteOrbitModel( + source.readTypedObject(GlonassSatelliteOrbitModel.CREATOR)) + .build(); + } + + @Override + public GlonassSatelliteEphemeris[] newArray(int size) { + return new GlonassSatelliteEphemeris[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassSatelliteEphemeris["); + builder.append("slotNumber = ").append(mSlotNumber); + builder.append(", healthState = ").append(mHealthState); + builder.append(", frameTimeSeconds = ").append(mFrameTimeSeconds); + builder.append(", ageInDays = ").append(mAgeInDays); + builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); + builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GlonassSatelliteEphemeris}. */ + public static final class Builder { + private int mSlotNumber; + private int mHealthState; + private double mFrameTimeSeconds; + private int mAgeInDays; + private GlonassSatelliteClockModel mSatelliteClockModel; + private GlonassSatelliteOrbitModel mSatelliteOrbitModel; + + /** + * Sets the L1/Satellite system (R), satellite number (slot number in sat. constellation). + */ + @NonNull + public Builder setSlotNumber(@IntRange(from = 1, to = 25) int slotNumber) { + mSlotNumber = slotNumber; + return this; + } + + /** Sets the health state (0=healthy, 1=unhealthy). */ + @NonNull + public Builder setHealthState(@IntRange(from = 0, to = 1) int healthState) { + mHealthState = healthState; + return this; + } + + /** Sets the message frame time in seconds of the UTC week (tk+nd*86400). */ + @NonNull + public Builder setFrameTimeSeconds(@FloatRange(from = 0.0f) double frameTimeSeconds) { + mFrameTimeSeconds = frameTimeSeconds; + return this; + } + + /** Sets the age of current information in days (E). */ + @NonNull + public Builder setAgeInDays(@IntRange(from = 0, to = 31) int ageInDays) { + mAgeInDays = ageInDays; + return this; + } + + /** Sets the satellite clock model. */ + @NonNull + public Builder setSatelliteClockModel( + @NonNull GlonassSatelliteClockModel satelliteClockModel) { + mSatelliteClockModel = satelliteClockModel; + return this; + } + + /** Sets the satellite orbit model. */ + @NonNull + public Builder setSatelliteOrbitModel( + @NonNull GlonassSatelliteOrbitModel satelliteOrbitModel) { + mSatelliteOrbitModel = satelliteOrbitModel; + return this; + } + + /** Builds a {@link GlonassSatelliteEphemeris}. */ + @NonNull + public GlonassSatelliteEphemeris build() { + return new GlonassSatelliteEphemeris(this); + } + } + + /** + * A class contains the set of parameters needed for Glonass satellite clock correction. + * + * <p>This is defined in RINEX 3.05 APPENDIX 10 and Glonass ICD v5.1 section 4.4. + */ + public static final class GlonassSatelliteClockModel implements Parcelable { + /** + * Time of the clock in seconds (UTC) + * + * <p>Corresponds to the 'Epoch' field within the 'SV/EPOCH/SV CLK' record of Glonass + * navigation message in RINEX 3.05 Table A10. + */ + private final long mTimeOfClockSeconds; + + /** Clock bias in seconds (-TauN). */ + private final double mClockBias; + + /** Frequency bias (+GammaN). */ + private final double mFrequencyBias; + + /** Frequency number. */ + private final int mFrequencyNumber; + + private GlonassSatelliteClockModel(Builder builder) { + Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); + Preconditions.checkArgumentInRange(builder.mClockBias, -0.002f, 0.002f, "ClockBias"); + Preconditions.checkArgumentInRange( + builder.mFrequencyBias, -9.32e-10f, 9.32e-10f, "FrequencyBias"); + Preconditions.checkArgumentInRange(builder.mFrequencyNumber, -7, 6, "FrequencyNumber"); + mTimeOfClockSeconds = builder.mTimeOfClockSeconds; + mClockBias = builder.mClockBias; + mFrequencyBias = builder.mFrequencyBias; + mFrequencyNumber = builder.mFrequencyNumber; + } + + /** Returns the time of clock in seconds (UTC). */ + @IntRange(from = 0) + public long getTimeOfClockSeconds() { + return mTimeOfClockSeconds; + } + + /** Returns the clock bias in seconds (-TauN). */ + @FloatRange(from = -0.002f, to = 0.002f) + public double getClockBias() { + return mClockBias; + } + + /** Returns the frequency bias (+GammaN). */ + @FloatRange(from = -9.32e-10f, to = 9.32e-10f) + public double getFrequencyBias() { + return mFrequencyBias; + } + + /** Returns the frequency number. */ + @IntRange(from = -7, to = 6) + public int getFrequencyNumber() { + return mFrequencyNumber; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mTimeOfClockSeconds); + dest.writeDouble(mClockBias); + dest.writeDouble(mFrequencyBias); + dest.writeInt(mFrequencyNumber); + } + + public static final @NonNull Parcelable.Creator<GlonassSatelliteClockModel> CREATOR = + new Parcelable.Creator<GlonassSatelliteClockModel>() { + @Override + public GlonassSatelliteClockModel createFromParcel(@NonNull Parcel source) { + return new GlonassSatelliteClockModel.Builder() + .setTimeOfClockSeconds(source.readLong()) + .setClockBias(source.readDouble()) + .setFrequencyBias(source.readDouble()) + .setFrequencyNumber(source.readInt()) + .build(); + } + + @Override + public GlonassSatelliteClockModel[] newArray(int size) { + return new GlonassSatelliteClockModel[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassSatelliteClockModel["); + builder.append("timeOfClockSeconds = ").append(mTimeOfClockSeconds); + builder.append(", clockBias = ").append(mClockBias); + builder.append(", frequencyBias = ").append(mFrequencyBias); + builder.append(", frequencyNumber = ").append(mFrequencyNumber); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GlonassSatelliteClockModel}. */ + public static final class Builder { + private long mTimeOfClockSeconds; + private double mClockBias; + private double mFrequencyBias; + private int mFrequencyNumber; + + /** Sets the time of clock in seconds (UTC). */ + @NonNull + public Builder setTimeOfClockSeconds(@IntRange(from = 0) long timeOfClockSeconds) { + mTimeOfClockSeconds = timeOfClockSeconds; + return this; + } + + /** Sets the clock bias in seconds (-TauN). */ + @NonNull + public Builder setClockBias(@FloatRange(from = -0.002f, to = 0.002f) double clockBias) { + mClockBias = clockBias; + return this; + } + + /** Sets the frequency bias (+GammaN). */ + @NonNull + public Builder setFrequencyBias( + @FloatRange(from = -9.32e-10f, to = 9.32e-10f) double frequencyBias) { + mFrequencyBias = frequencyBias; + return this; + } + + /** Sets the frequency number. */ + @NonNull + public Builder setFrequencyNumber(@IntRange(from = -7, to = 6) int frequencyNumber) { + mFrequencyNumber = frequencyNumber; + return this; + } + + /** Builds a {@link GlonassSatelliteClockModel}. */ + @NonNull + public GlonassSatelliteClockModel build() { + return new GlonassSatelliteClockModel(this); + } + } + } + + /** + * A class contains the set of parameters needed for Glonass satellite orbit correction. + * + * <p>This is defined in RINEX 3.05 APPENDIX 10 and Glonass ICD v5.1 section 4.4. + */ + public static final class GlonassSatelliteOrbitModel implements Parcelable { + /** X position in kilometers. */ + private final double mX; + + /** X velocity in kilometers per second. */ + private final double mXDot; + + /** X acceleration in kilometers per second squared. */ + private final double mXAccel; + + /** Y position in kilometers. */ + private final double mY; + + /** Y velocity in kilometers per second. */ + private final double mYDot; + + /** Y acceleration in kilometers per second squared. */ + private final double mYAccel; + + /** Z position in kilometers. */ + private final double mZ; + + /** Z velocity in kilometers per second. */ + private final double mZDot; + + /** Z acceleration in kilometers per second squared. */ + private final double mZAccel; + + private GlonassSatelliteOrbitModel(Builder builder) { + Preconditions.checkArgumentInRange(builder.mX, -2.7e4f, 2.7e4f, "X"); + Preconditions.checkArgumentInRange(builder.mXDot, -4.3f, 4.3f, "XDot"); + Preconditions.checkArgumentInRange(builder.mXAccel, -6.2e-9f, 6.2e-9f, "XAccel"); + Preconditions.checkArgumentInRange(builder.mY, -2.7e4f, 2.7e4f, "Y"); + Preconditions.checkArgumentInRange(builder.mYDot, -4.3f, 4.3f, "YDot"); + Preconditions.checkArgumentInRange(builder.mYAccel, -6.2e-9f, 6.2e-9f, "YAccel"); + Preconditions.checkArgumentInRange(builder.mZ, -2.7e4f, 2.7e4f, "Z"); + Preconditions.checkArgumentInRange(builder.mZDot, -4.3f, 4.3f, "ZDot"); + Preconditions.checkArgumentInRange(builder.mZAccel, -6.2e-9f, 6.2e-9f, "ZAccel"); + mX = builder.mX; + mXDot = builder.mXDot; + mXAccel = builder.mXAccel; + mY = builder.mY; + mYDot = builder.mYDot; + mYAccel = builder.mYAccel; + mZ = builder.mZ; + mZDot = builder.mZDot; + mZAccel = builder.mZAccel; + } + + /** Returns the X position in kilometers. */ + @FloatRange(from = -2.7e4f, to = 2.7e4f) + public double getX() { + return mX; + } + + /** Returns the X velocity in kilometers per second. */ + @FloatRange(from = -4.3f, to = 4.3f) + public double getXDot() { + return mXDot; + } + + /** Returns the X acceleration in kilometers per second squared. */ + @FloatRange(from = -6.2e-9f, to = 6.2e-9f) + public double getXAccel() { + return mXAccel; + } + + /** Returns the Y position in kilometers. */ + @FloatRange(from = -2.7e4f, to = 2.7e4f) + public double getY() { + return mY; + } + + /** Returns the Y velocity in kilometers per second. */ + @FloatRange(from = -4.3f, to = 4.3f) + public double getYDot() { + return mYDot; + } + + /** Returns the Y acceleration in kilometers per second squared. */ + @FloatRange(from = -6.2e-9f, to = 6.2e-9f) + public double getYAccel() { + return mYAccel; + } + + /** Returns the Z position in kilometers. */ + @FloatRange(from = -2.7e4f, to = 2.7e4f) + public double getZ() { + return mZ; + } + + /** Returns the Z velocity in kilometers per second. */ + @FloatRange(from = -4.3f, to = 4.3f) + public double getZDot() { + return mZDot; + } + + /** Returns the Z acceleration in kilometers per second squared. */ + @FloatRange(from = -6.2e-9f, to = 6.2e-9f) + public double getZAccel() { + return mZAccel; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeDouble(mX); + dest.writeDouble(mXDot); + dest.writeDouble(mXAccel); + dest.writeDouble(mY); + dest.writeDouble(mYDot); + dest.writeDouble(mYAccel); + dest.writeDouble(mZ); + dest.writeDouble(mZDot); + dest.writeDouble(mZAccel); + } + + public static final @NonNull Parcelable.Creator<GlonassSatelliteOrbitModel> CREATOR = + new Parcelable.Creator<GlonassSatelliteOrbitModel>() { + @Override + public GlonassSatelliteOrbitModel createFromParcel(@NonNull Parcel source) { + return new GlonassSatelliteOrbitModel.Builder() + .setX(source.readDouble()) + .setXDot(source.readDouble()) + .setXAccel(source.readDouble()) + .setY(source.readDouble()) + .setYDot(source.readDouble()) + .setYAccel(source.readDouble()) + .setZ(source.readDouble()) + .setZDot(source.readDouble()) + .setZAccel(source.readDouble()) + .build(); + } + + @Override + public GlonassSatelliteOrbitModel[] newArray(int size) { + return new GlonassSatelliteOrbitModel[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GlonassSatelliteOrbitModel["); + builder.append("x = ").append(mX); + builder.append(", xDot = ").append(mXDot); + builder.append(", xAccel = ").append(mXAccel); + builder.append(", y = ").append(mY); + builder.append(", yDot = ").append(mYDot); + builder.append(", yAccel = ").append(mYAccel); + builder.append(", z = ").append(mZ); + builder.append(", zDot = ").append(mZDot); + builder.append(", zAccel = ").append(mZAccel); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GlonassSatelliteOrbitModel}. */ + public static final class Builder { + private double mX; + private double mXDot; + private double mXAccel; + private double mY; + private double mYDot; + private double mYAccel; + private double mZ; + private double mZDot; + private double mZAccel; + + /** Sets the X position in kilometers. */ + @NonNull + public Builder setX(@FloatRange(from = -2.7e4f, to = 2.7e4f) double x) { + mX = x; + return this; + } + + /** Sets the X velocity in kilometers per second. */ + @NonNull + public Builder setXDot(@FloatRange(from = -4.3f, to = 4.3f) double xDot) { + mXDot = xDot; + return this; + } + + /** Sets the X acceleration in kilometers per second squared. */ + @NonNull + public Builder setXAccel(@FloatRange(from = -6.2e-9f, to = 6.2e-9f) double xAccel) { + mXAccel = xAccel; + return this; + } + + /** Sets the Y position in kilometers. */ + @NonNull + public Builder setY(@FloatRange(from = -2.7e4f, to = 2.7e4f) double y) { + mY = y; + return this; + } + + /** Sets the Y velocity in kilometers per second. */ + @NonNull + public Builder setYDot(@FloatRange(from = -4.3f, to = 4.3f) double yDot) { + mYDot = yDot; + return this; + } + + /** Sets the Y acceleration in kilometers per second squared. */ + @NonNull + public Builder setYAccel(@FloatRange(from = -6.2e-9f, to = 6.2e-9f) double yAccel) { + mYAccel = yAccel; + return this; + } + + /** Sets the Z position in kilometers. */ + @NonNull + public Builder setZ(@FloatRange(from = -2.7e4f, to = 2.7e4f) double z) { + mZ = z; + return this; + } + + /** Sets the Z velocity in kilometers per second. */ + @NonNull + public Builder setZDot(@FloatRange(from = -4.3f, to = 4.3f) double zDot) { + mZDot = zDot; + return this; + } + + /** Sets the Z acceleration in kilometers per second squared. */ + @NonNull + public Builder setZAccel(@FloatRange(from = -6.2e-9f, to = 6.2e-9f) double zAccel) { + mZAccel = zAccel; + return this; + } + + /** Builds a {@link GlonassSatelliteOrbitModel}. */ + @NonNull + public GlonassSatelliteOrbitModel build() { + return new GlonassSatelliteOrbitModel(this); + } + } + } +} diff --git a/location/java/android/location/GnssAlmanac.java b/location/java/android/location/GnssAlmanac.java new file mode 100644 index 000000000000..6466e45a965e --- /dev/null +++ b/location/java/android/location/GnssAlmanac.java @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains almanac parameters for GPS, QZSS, Galileo, Beidou. + * + * <p>For Beidou, this is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.15. + * + * <p>For GPS, this is defined in IS-GPS-200 section 20.3.3.5.1.2. + * + * <p>For QZSS, this is defined in IS-QZSS-PNT section 4.1.2.6. + * + * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD-v2.1 section 5.1.10. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GnssAlmanac implements Parcelable { + /** + * Almanac issue date in milliseconds (UTC). + * + * <p>This is unused for GPS/QZSS/Baidou. + */ + private final long mIssueDateMillis; + + /** + * Almanac issue of data. + * + * <p>This is unused for GPS/QZSS/Baidou. + */ + private final int mIod; + + /** + * Almanac reference week number. + * + * <p>For GPS and QZSS, this is GPS week number (modulo 1024). + * + * <p>For Beidou, this is Baidou week number (modulo 8192). + * + * <p>For Galileo, this is modulo 4 representation of the Galileo week number. + */ + private final int mWeekNumber; + + /** Almanac reference time in seconds. */ + private final int mToaSeconds; + + /** The list of GnssSatelliteAlmanacs. */ + @NonNull private final List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; + + private GnssAlmanac(Builder builder) { + Preconditions.checkArgument(builder.mIssueDateMillis >= 0); + Preconditions.checkArgument(builder.mIod >= 0); + Preconditions.checkArgument(builder.mWeekNumber >= 0); + Preconditions.checkArgumentInRange(builder.mToaSeconds, 0, 604800, "ToaSeconds"); + Preconditions.checkNotNull( + builder.mGnssSatelliteAlmanacs, "GnssSatelliteAlmanacs cannot be null"); + mIssueDateMillis = builder.mIssueDateMillis; + mIod = builder.mIod; + mWeekNumber = builder.mWeekNumber; + mToaSeconds = builder.mToaSeconds; + mGnssSatelliteAlmanacs = + Collections.unmodifiableList(new ArrayList<>(builder.mGnssSatelliteAlmanacs)); + } + + /** Returns the almanac issue date in milliseconds (UTC). */ + @IntRange(from = 0) + public long getIssueDateMillis() { + return mIssueDateMillis; + } + + /** Returns the almanac issue of data. */ + @IntRange(from = 0) + public int getIod() { + return mIod; + } + + /** + * Returns the almanac reference week number. + * + * <p>For GPS and QZSS, this is GPS week number (modulo 1024). + * + * <p>For Beidou, this is Baidou week number (modulo 8192). + * + * <p>For Galileo, this is modulo 4 representation of the Galileo week number. + */ + @IntRange(from = 0) + public int getWeekNumber() { + return mWeekNumber; + } + + /** Returns the almanac reference time in seconds. */ + @IntRange(from = 0, to = 604800) + public int getToaSeconds() { + return mToaSeconds; + } + + /** Returns the list of GnssSatelliteAlmanacs. */ + @NonNull + public List<GnssSatelliteAlmanac> getGnssSatelliteAlmanacs() { + return mGnssSatelliteAlmanacs; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mIssueDateMillis); + dest.writeInt(mIod); + dest.writeInt(mWeekNumber); + dest.writeInt(mToaSeconds); + dest.writeTypedList(mGnssSatelliteAlmanacs); + } + + public static final @NonNull Creator<GnssAlmanac> CREATOR = + new Creator<GnssAlmanac>() { + @Override + public GnssAlmanac createFromParcel(Parcel in) { + GnssAlmanac.Builder gnssAlmanac = new GnssAlmanac.Builder(); + gnssAlmanac.setIssueDateMillis(in.readLong()); + gnssAlmanac.setIod(in.readInt()); + gnssAlmanac.setWeekNumber(in.readInt()); + gnssAlmanac.setToaSeconds(in.readInt()); + List<GnssSatelliteAlmanac> satelliteAlmanacs = new ArrayList<>(); + in.readTypedList(satelliteAlmanacs, GnssSatelliteAlmanac.CREATOR); + gnssAlmanac.setGnssSatelliteAlmanacs(satelliteAlmanacs); + return gnssAlmanac.build(); + } + + @Override + public GnssAlmanac[] newArray(int size) { + return new GnssAlmanac[size]; + } + }; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("GnssAlmanac["); + builder.append("issueDateMillis=").append(mIssueDateMillis); + builder.append(", iod=").append(mIod); + builder.append(", weekNumber=").append(mWeekNumber); + builder.append(", toaSeconds=").append(mToaSeconds); + builder.append(", satelliteAlmanacs=").append(mGnssSatelliteAlmanacs); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GnssAlmanac}. */ + public static final class Builder { + private long mIssueDateMillis; + private int mIod; + private int mWeekNumber; + private int mToaSeconds; + private List<GnssSatelliteAlmanac> mGnssSatelliteAlmanacs; + + /** Sets the almanac issue date in milliseconds (UTC). */ + @NonNull + public Builder setIssueDateMillis(@IntRange(from = 0) long issueDateMillis) { + mIssueDateMillis = issueDateMillis; + return this; + } + + /** Sets the almanac issue of data. */ + @NonNull + public Builder setIod(@IntRange(from = 0) int iod) { + mIod = iod; + return this; + } + + /** + * Sets the almanac reference week number. + * + * <p>For GPS and QZSS, this is GPS week number (modulo 1024). + * + * <p>For Beidou, this is Baidou week number (modulo 8192). + * + * <p>For Galileo, this is modulo 4 representation of the Galileo week number. + */ + @NonNull + public Builder setWeekNumber(@IntRange(from = 0) int weekNumber) { + mWeekNumber = weekNumber; + return this; + } + + /** Sets the almanac reference time in seconds. */ + @NonNull + public Builder setToaSeconds(@IntRange(from = 0, to = 604800) int toaSeconds) { + mToaSeconds = toaSeconds; + return this; + } + + /** Sets the list of GnssSatelliteAlmanacs. */ + @NonNull + public Builder setGnssSatelliteAlmanacs( + @NonNull List<GnssSatelliteAlmanac> gnssSatelliteAlmanacs) { + mGnssSatelliteAlmanacs = gnssSatelliteAlmanacs; + return this; + } + + /** Builds a {@link GnssAlmanac} instance as specified by this builder. */ + @NonNull + public GnssAlmanac build() { + return new GnssAlmanac(this); + } + } + + /** + * A class contains almanac parameters for GPS, QZSS, Galileo, Beidou. + * + * <p>For Beidou, this is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.15. + * + * <p>For GPS, this is defined in IS-GPS-200 section 20.3.3.5.1.2. + * + * <p>For QZSS, this is defined in IS-QZSS-PNT section 4.1.2.6. + * + * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD-v2.1 section 5.1.10. + */ + public static final class GnssSatelliteAlmanac implements Parcelable { + /** The PRN number of the GNSS satellite. */ + private final int mSvid; + + /** + * Satellite health information. + * + * <p>For GPS, this is satellite subframe 4 and 5, page 25 6-bit health code as defined in + * IS-GPS-200 Table 20-VIII expressed in integer form. + * + * <p>For QZSS, this is the 5-bit health code as defined in IS-QZSS-PNT, Table 4.1.2-5-2 + * expressed in integer form. + * + * <p>For Beidou, this is 1-bit health information. (0=healthy, 1=unhealthy). + * + * <p>For Galileo, this is 6-bit health, bit 0 and 1 is for E5a, bit 2 and 3 is for E5b, bit + * 4 and 5 is for E1b. + */ + private final int mSvHealth; + + /** Eccentricity. */ + private final double mEccentricity; + + /** + * Inclination in semi-circles. + * + * <p>For GPS and Galileo, this is the difference between the inclination angle at reference + * time and the nominal inclination in semi-circles. + * + * <p>For Beidou and QZSS, this is the inclination angle at reference time in semi-circles. + */ + private final double mInclination; + + /** Argument of perigee in semi-circles. */ + private final double mOmega; + + /** Longitude of ascending node of orbital plane at weekly epoch in semi-circles. */ + private final double mOmega0; + + /** Rate of right ascension in semi-circles per second. */ + private final double mOmegaDot; + + /** + * Square root of semi-major axis in square root of meters. + * + * <p>For Galileo, this is the difference with respect to the square root of the nominal + * semi-major axis in square root of meters. + */ + private final double mRootA; + + /** Mean anomaly at reference time in semi-circles. */ + private final double mM0; + + /** Satellite clock time bias correction coefficient in seconds. */ + private final double mAf0; + + /** Satellite clock time drift correction coefficient in seconds per second. */ + private final double mAf1; + + private GnssSatelliteAlmanac(Builder builder) { + Preconditions.checkArgument(builder.mSvid > 0); + Preconditions.checkArgument(builder.mSvHealth >= 0); + Preconditions.checkArgument(builder.mEccentricity >= 0.0f); + Preconditions.checkArgumentInRange(builder.mInclination, -1.0f, 1.0f, "Inclination"); + Preconditions.checkArgumentInRange(builder.mOmega, -1.0f, 1.0f, "Omega"); + Preconditions.checkArgumentInRange(builder.mOmega0, -1.0f, 1.0f, "Omega0"); + Preconditions.checkArgumentInRange(builder.mOmegaDot, -1.0f, 1.0f, "OmegaDot"); + Preconditions.checkArgumentInRange(builder.mRootA, 0.0f, 8192.0f, "RootA"); + Preconditions.checkArgumentInRange(builder.mM0, -1.0f, 1.0f, "M0"); + Preconditions.checkArgumentInRange(builder.mAf0, -0.0625f, 0.0625f, "Af0"); + Preconditions.checkArgumentInRange(builder.mAf1, -1.5e-8f, 1.5e-8f, "Af1"); + mSvid = builder.mSvid; + mSvHealth = builder.mSvHealth; + mEccentricity = builder.mEccentricity; + mInclination = builder.mInclination; + mOmega = builder.mOmega; + mOmega0 = builder.mOmega0; + mOmegaDot = builder.mOmegaDot; + mRootA = builder.mRootA; + mM0 = builder.mM0; + mAf0 = builder.mAf0; + mAf1 = builder.mAf1; + } + + /** Returns the PRN number of the GNSS satellite. */ + @IntRange(from = 1) + public int getSvid() { + return mSvid; + } + + /** + * Returns the satellite health information. + * + * <p>For GPS, this is satellite subframe 4 and 5, page 25 6-bit health code as defined in + * IS-GPS-200 Table 20-VIII expressed in integer form. + * + * <p>For QZSS, this is the 5-bit health code as defined in IS-QZSS-PNT, Table 4.1.2-5-2 + * expressed in integer form. + * + * <p>For Beidou, this is 1-bit health information. (0=healthy, 1=unhealthy). + * + * <p>For Galileo, this is 6-bit health, bit 0 and 1 is for E5a, bit 2 and 3 is for E5b, + * bit 4 and 5 is for E1b. + */ + @IntRange(from = 0) + public int getSvHealth() { + return mSvHealth; + } + + /** Returns the eccentricity. */ + @FloatRange(from = 0.0f) + public double getEccentricity() { + return mEccentricity; + } + + /** + * Returns the inclination in semi-circles. + * + * <p>For GPS and Galileo, this is the difference between the inclination angle at reference + * time and the nominal inclination in semi-circles. + * + * <p>For Beidou and QZSS, this is the inclination angle at reference time in semi-circles. + */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getInclination() { + return mInclination; + } + + /** Returns the argument of perigee in semi-circles. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getOmega() { + return mOmega; + } + + /** + * Returns the longitude of ascending node of orbital plane at weekly epoch in semi-circles. + */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getOmega0() { + return mOmega0; + } + + /** Returns the rate of right ascension in semi-circles per second. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getOmegaDot() { + return mOmegaDot; + } + + /** + * Returns the square root of semi-major axis in square root of meters. + * + * <p>For Galileo, this is the difference with respect to the square root of the nominal + * semi-major axis in square root of meters. + */ + @FloatRange(from = 0.0f, to = 8192.0f) + public double getRootA() { + return mRootA; + } + + /** Returns the mean anomaly at reference time in semi-circles. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getM0() { + return mM0; + } + + /** Returns the satellite clock time bias correction coefficient in seconds. */ + @FloatRange(from = -0.0625f, to = 0.0625f) + public double getAf0() { + return mAf0; + } + + /** Returns the satellite clock time drift correction coefficient in seconds per second. */ + @FloatRange(from = -1.5e-8f, to = 1.5e-8f) + public double getAf1() { + return mAf1; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSvid); + dest.writeInt(mSvHealth); + dest.writeDouble(mEccentricity); + dest.writeDouble(mInclination); + dest.writeDouble(mOmega); + dest.writeDouble(mOmega0); + dest.writeDouble(mOmegaDot); + dest.writeDouble(mRootA); + dest.writeDouble(mM0); + dest.writeDouble(mAf0); + dest.writeDouble(mAf1); + } + + public static final @NonNull Creator<GnssSatelliteAlmanac> CREATOR = + new Creator<GnssSatelliteAlmanac>() { + @Override + public GnssSatelliteAlmanac createFromParcel(Parcel in) { + return new GnssSatelliteAlmanac( + new Builder() + .setSvid(in.readInt()) + .setSvHealth(in.readInt()) + .setEccentricity(in.readDouble()) + .setInclination(in.readDouble()) + .setOmega(in.readDouble()) + .setOmega0(in.readDouble()) + .setOmegaDot(in.readDouble()) + .setRootA(in.readDouble()) + .setM0(in.readDouble()) + .setAf0(in.readDouble()) + .setAf1(in.readDouble())); + } + + @Override + public GnssSatelliteAlmanac[] newArray(int size) { + return new GnssSatelliteAlmanac[size]; + } + }; + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GnssSatelliteAlmanac["); + builder.append("svid = ").append(mSvid); + builder.append(", svHealth = ").append(mSvHealth); + builder.append(", eccentricity = ").append(mEccentricity); + builder.append(", inclination = ").append(mInclination); + builder.append(", omega = ").append(mOmega); + builder.append(", omega0 = ").append(mOmega0); + builder.append(", omegaDot = ").append(mOmegaDot); + builder.append(", rootA = ").append(mRootA); + builder.append(", m0 = ").append(mM0); + builder.append(", af0 = ").append(mAf0); + builder.append(", af1 = ").append(mAf1); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GnssSatelliteAlmanac}. */ + public static final class Builder { + private int mSvid; + private int mSvHealth; + private double mEccentricity; + private double mInclination; + private double mOmega; + private double mOmega0; + private double mOmegaDot; + private double mRootA; + private double mM0; + private double mAf0; + private double mAf1; + + /** Sets the PRN number of the GNSS satellite. */ + @NonNull + public Builder setSvid(@IntRange(from = 1) int svid) { + mSvid = svid; + return this; + } + + /** + * Sets the satellite health information. + * + * <p>For GPS, this is satellite subframe 4 and 5, page 25 6-bit health code as defined + * in IS-GPS-200 Table 20-VIII expressed in integer form. + * + * <p>For QZSS, this is the 5-bit health code as defined in IS-QZSS-PNT, Table 4.1.2-5-2 + * expressed in integer form. + * + * <p>For Beidou, this is 1-bit health information. (0=healthy, 1=unhealthy). + * + * <p>For Galileo, this is 6-bit health, bit 0 and 1 is for E5a, + * bit 2 and 3 is for E5b, bit 4 and 5 is for E1b. + */ + @NonNull + public Builder setSvHealth(@IntRange(from = 0) int svHealth) { + mSvHealth = svHealth; + return this; + } + + /** Sets the eccentricity. */ + @NonNull + public Builder setEccentricity(@FloatRange(from = 0.0f) double eccentricity) { + mEccentricity = eccentricity; + return this; + } + + /** + * Sets the inclination in semi-circles. + * + * <p>For GPS and Galileo, this is the difference between the inclination angle at + * reference time and the nominal inclination in semi-circles. + * + * <p>For Beidou and QZSS, this is the inclination angle at reference time in + * semi-circles. + */ + @NonNull + public Builder setInclination(@FloatRange(from = -1.0f, to = 1.0f) double inclination) { + mInclination = inclination; + return this; + } + + /** Sets the argument of perigee in semi-circles. */ + @NonNull + public Builder setOmega(@FloatRange(from = -1.0f, to = 1.0f) double omega) { + mOmega = omega; + return this; + } + + /** + * Sets the longitude of ascending node of orbital plane at weekly epoch in + * semi-circles. + */ + @NonNull + public Builder setOmega0(@FloatRange(from = -1.0f, to = 1.0f) double omega0) { + mOmega0 = omega0; + return this; + } + + /** Sets the rate of right ascension in semi-circles per second. */ + @NonNull + public Builder setOmegaDot(@FloatRange(from = -1.0f, to = 1.0f) double omegaDot) { + mOmegaDot = omegaDot; + return this; + } + + /** + * Sets the square root of semi-major axis in square root of meters. + * + * <p>For Galileo, this is the difference with respect to the square root of the nominal + * semi-major axis in square root of meters. + */ + @NonNull + public Builder setRootA(@FloatRange(from = 0.0f, to = 8192.0f) double rootA) { + mRootA = rootA; + return this; + } + + /** Sets the mean anomaly at reference time in semi-circles. */ + @NonNull + public Builder setM0(@FloatRange(from = -1.0f, to = 1.0f) double m0) { + mM0 = m0; + return this; + } + + /** Sets the satellite clock time bias correction coefficient in seconds. */ + @NonNull + public Builder setAf0(@FloatRange(from = -0.0625f, to = 0.0625f) double af0) { + mAf0 = af0; + return this; + } + + /** Sets the satellite clock time drift correction coefficient in seconds per second. */ + @NonNull + public Builder setAf1(@FloatRange(from = -1.5e-8f, to = 1.5e-8f) double af1) { + mAf1 = af1; + return this; + } + + /** Builds a {@link GnssSatelliteAlmanac} instance as specified by this builder. */ + @NonNull + public GnssSatelliteAlmanac build() { + return new GnssSatelliteAlmanac(this); + } + } + } +} diff --git a/location/java/android/location/GnssAssistance.aidl b/location/java/android/location/GnssAssistance.aidl new file mode 100644 index 000000000000..2745683ec330 --- /dev/null +++ b/location/java/android/location/GnssAssistance.aidl @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.location; + +parcelable GnssAssistance;
\ No newline at end of file diff --git a/location/java/android/location/GnssAssistance.java b/location/java/android/location/GnssAssistance.java new file mode 100644 index 000000000000..e941122f8c79 --- /dev/null +++ b/location/java/android/location/GnssAssistance.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains GNSS assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GnssAssistance implements Parcelable { + + /** GPS assistance. */ + @Nullable private final GpsAssistance mGpsAssistance; + + /** Glonass assistance. */ + @Nullable private final GlonassAssistance mGlonassAssistance; + + /** Galileo assistance. */ + @Nullable private final GalileoAssistance mGalileoAssistance; + + /** Beidou assistance. */ + @Nullable private final BeidouAssistance mBeidouAssistance; + + /** QZSS assistance. */ + @Nullable private final QzssAssistance mQzssAssistance; + + private GnssAssistance(Builder builder) { + mGpsAssistance = builder.mGpsAssistance; + mGlonassAssistance = builder.mGlonassAssistance; + mGalileoAssistance = builder.mGalileoAssistance; + mBeidouAssistance = builder.mBeidouAssistance; + mQzssAssistance = builder.mQzssAssistance; + } + + /** Returns the GPS assistance. */ + @Nullable + public GpsAssistance getGpsAssistance() { + return mGpsAssistance; + } + + /** Returns the Glonass assistance. */ + @Nullable + public GlonassAssistance getGlonassAssistance() { + return mGlonassAssistance; + } + + /** Returns the Galileo assistance. */ + @Nullable + public GalileoAssistance getGalileoAssistance() { + return mGalileoAssistance; + } + + /** Returns the Beidou assistance. */ + @Nullable + public BeidouAssistance getBeidouAssistance() { + return mBeidouAssistance; + } + + /** Returns the Qzss assistance. */ + @Nullable + public QzssAssistance getQzssAssistance() { + return mQzssAssistance; + } + + public static final @NonNull Creator<GnssAssistance> CREATOR = + new Creator<GnssAssistance>() { + @Override + @NonNull + public GnssAssistance createFromParcel(Parcel in) { + return new GnssAssistance.Builder() + .setGpsAssistance(in.readTypedObject(GpsAssistance.CREATOR)) + .setGlonassAssistance(in.readTypedObject(GlonassAssistance.CREATOR)) + .setGalileoAssistance(in.readTypedObject(GalileoAssistance.CREATOR)) + .setBeidouAssistance(in.readTypedObject(BeidouAssistance.CREATOR)) + .setQzssAssistance(in.readTypedObject(QzssAssistance.CREATOR)) + .build(); + } + + @Override + public GnssAssistance[] newArray(int size) { + return new GnssAssistance[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeTypedObject(mGpsAssistance, flags); + parcel.writeTypedObject(mGlonassAssistance, flags); + parcel.writeTypedObject(mGalileoAssistance, flags); + parcel.writeTypedObject(mBeidouAssistance, flags); + parcel.writeTypedObject(mQzssAssistance, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GnssAssistance["); + builder.append("gpsAssistance = ").append(mGpsAssistance); + builder.append(", glonassAssistance = ").append(mGlonassAssistance); + builder.append(", galileoAssistance = ").append(mGalileoAssistance); + builder.append(", beidouAssistance = ").append(mBeidouAssistance); + builder.append(", qzssAssistance = ").append(mQzssAssistance); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GnssAssistance}. */ + public static final class Builder { + private GpsAssistance mGpsAssistance; + private GlonassAssistance mGlonassAssistance; + private GalileoAssistance mGalileoAssistance; + private BeidouAssistance mBeidouAssistance; + private QzssAssistance mQzssAssistance; + + /** Sets the GPS assistance. */ + @NonNull + public Builder setGpsAssistance(@Nullable GpsAssistance gpsAssistance) { + mGpsAssistance = gpsAssistance; + return this; + } + + /** Sets the Glonass assistance. */ + @NonNull + public Builder setGlonassAssistance(@Nullable GlonassAssistance glonassAssistance) { + mGlonassAssistance = glonassAssistance; + return this; + } + + /** Sets the Galileo assistance. */ + @NonNull + public Builder setGalileoAssistance(@Nullable GalileoAssistance galileoAssistance) { + mGalileoAssistance = galileoAssistance; + return this; + } + + /** Sets the Beidou assistance. */ + @NonNull + public Builder setBeidouAssistance(@Nullable BeidouAssistance beidouAssistance) { + mBeidouAssistance = beidouAssistance; + return this; + } + + /** Sets the QZSS assistance. */ + @NonNull + public Builder setQzssAssistance(@Nullable QzssAssistance qzssAssistance) { + mQzssAssistance = qzssAssistance; + return this; + } + + /** Builds a {@link GnssAssistance} instance as specified by this builder. */ + @NonNull + public GnssAssistance build() { + return new GnssAssistance(this); + } + } + + /** A class contains GNSS corrections for satellites. */ + public static final class GnssSatelliteCorrections implements Parcelable { + /** + * Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle (SV), or OSN + * number for Glonass. + * + * <p>The distinction is made by looking at the constellation field. Values must be in the + * range of: + * + * <p>- GPS: 1-32 + * + * <p>- GLONASS: 1-25 + * + * <p>- QZSS: 183-206 + * + * <p>- Galileo: 1-36 + * + * <p>- Beidou: 1-63 + */ + @IntRange(from = 1, to = 206) + int mSvid; + + /** List of Ionospheric corrections */ + @NonNull List<IonosphericCorrection> mIonosphericCorrections; + + /** + * Creates a new {@link GnssSatelliteCorrections} instance. + * + * @param svid The Pseudo-random or satellite ID number for the satellite, a.k.a. Space + * Vehicle (SV), or OSN number for Glonass. + * <p>The distinction is made by looking at the constellation field. Values must be in + * the range of: + * <p>- GPS: 1-32 + * <p>- GLONASS: 1-25 + * <p>- QZSS: 183-206 + * <p>- Galileo: 1-36 + * <p>- Beidou: 1-63 + * @param ionosphericCorrections The list of Ionospheric corrections. + */ + public GnssSatelliteCorrections( + @IntRange(from = 1, to = 206) int svid, + @NonNull final List<IonosphericCorrection> ionosphericCorrections) { + // Allow SV ID beyond the range to support potential future extensibility. + Preconditions.checkArgument(svid >= 1); + Preconditions.checkNotNull( + ionosphericCorrections, "IonosphericCorrections cannot be null"); + mSvid = svid; + mIonosphericCorrections = + Collections.unmodifiableList(new ArrayList<>(ionosphericCorrections)); + } + + /** + * Returns the Pseudo-random or satellite ID number for the satellite, a.k.a. Space Vehicle + * (SV), or OSN number for Glonass. + * + * <p>The distinction is made by looking at the constellation field. Values must be in the + * range of: + * + * <p>- GPS: 1-32 + * + * <p>- GLONASS: 1-25 + * + * <p>- QZSS: 183-206 + * + * <p>- Galileo: 1-36 + * + * <p>- Beidou: 1-63 + */ + @IntRange(from = 1, to = 206) + public int getSvid() { + return mSvid; + } + + /** Returns the list of Ionospheric corrections. */ + @NonNull + public List<IonosphericCorrection> getIonosphericCorrections() { + return mIonosphericCorrections; + } + + public static final @NonNull Creator<GnssSatelliteCorrections> CREATOR = + new Creator<GnssSatelliteCorrections>() { + @Override + @NonNull + public GnssSatelliteCorrections createFromParcel(Parcel in) { + int svid = in.readInt(); + List<IonosphericCorrection> ionosphericCorrections = new ArrayList<>(); + in.readTypedList(ionosphericCorrections, IonosphericCorrection.CREATOR); + return new GnssSatelliteCorrections(svid, ionosphericCorrections); + } + + @Override + public GnssSatelliteCorrections[] newArray(int size) { + return new GnssSatelliteCorrections[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSvid); + parcel.writeTypedList(mIonosphericCorrections, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GnssSatelliteCorrections["); + builder.append("svid = ").append(mSvid); + builder.append(", ionosphericCorrections = ").append(mIonosphericCorrections); + builder.append("]"); + return builder.toString(); + } + } +} diff --git a/location/java/android/location/GnssCorrectionComponent.java b/location/java/android/location/GnssCorrectionComponent.java new file mode 100644 index 000000000000..f55dde1c9228 --- /dev/null +++ b/location/java/android/location/GnssCorrectionComponent.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class that contains Gnss correction associated with a component (e.g. the Ionospheric error). + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GnssCorrectionComponent implements Parcelable { + /** + * Uniquely identifies the source of correction (e.g. "Klobuchar" for ionospheric corrections). + * Clients should not depend on the value of the source key but, rather, can compare + * before/after to detect changes. + */ + @NonNull private final String mSourceKey; + + /** The correction is only applicable during this time interval. */ + @NonNull private final GnssInterval mValidityInterval; + + /** Pseudorange correction. */ + @NonNull private final PseudorangeCorrection mPseudorangeCorrection; + + /** + * Creates a GnssCorrectionComponent. + * + * @param sourceKey Uniquely identifies the source of correction (e.g. "Klobuchar" for + * ionospheric corrections). Clients should not depend on the value of the source key but, + * rather, can compare before/after to detect changes. + * @param validityInterval The correction is only applicable during this time interval. + * @param pseudorangeCorrection Pseudorange correction. + */ + public GnssCorrectionComponent( + @NonNull String sourceKey, + @NonNull GnssInterval validityInterval, + @NonNull PseudorangeCorrection pseudorangeCorrection) { + Preconditions.checkNotNull(sourceKey, "SourceKey cannot be null"); + Preconditions.checkNotNull(validityInterval, "ValidityInterval cannot be null"); + Preconditions.checkNotNull(pseudorangeCorrection, "PseudorangeCorrection cannot be null"); + mSourceKey = sourceKey; + mValidityInterval = validityInterval; + mPseudorangeCorrection = pseudorangeCorrection; + } + + /** Returns the source key of the correction. */ + @NonNull + public String getSourceKey() { + return mSourceKey; + } + + /** Returns the validity interval of the correction. */ + @NonNull + public GnssInterval getValidityInterval() { + return mValidityInterval; + } + + /** Returns the pseudorange correction. */ + @NonNull + public PseudorangeCorrection getPseudorangeCorrection() { + return mPseudorangeCorrection; + } + + public static final @NonNull Creator<GnssCorrectionComponent> CREATOR = + new Creator<GnssCorrectionComponent>() { + @Override + @NonNull + public GnssCorrectionComponent createFromParcel(Parcel in) { + String sourceKey = in.readString8(); + GnssInterval validityInterval = in.readTypedObject(GnssInterval.CREATOR); + PseudorangeCorrection pseudorangeCorrection = + in.readTypedObject(PseudorangeCorrection.CREATOR); + return new GnssCorrectionComponent( + sourceKey, validityInterval, pseudorangeCorrection); + } + + @Override + public GnssCorrectionComponent[] newArray(int size) { + return new GnssCorrectionComponent[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mSourceKey); + dest.writeTypedObject(mValidityInterval, flags); + dest.writeTypedObject(mPseudorangeCorrection, flags); + } + + /** + * Time interval referenced against the GPS epoch. The start must be less than or equal to the + * end. When the start equals the end, the interval is empty. + */ + public static final class GnssInterval implements Parcelable { + /** + * Inclusive start of the interval in milliseconds since the GPS epoch. A timestamp matching + * this interval will have to be the same or after the start. Required as a reference time + * for the initial correction value and its rate of change over time. + */ + private final long mStartMillisSinceGpsEpoch; + + /** + * Exclusive end of the interval in milliseconds since the GPS epoch. If specified, a + * timestamp matching this interval will have to be before the end. + */ + private final long mEndMillisSinceGpsEpoch; + + /** + * Creates a GnssInterval. + * + * @param startMillisSinceGpsEpoch Inclusive start of the interval in milliseconds since the + * GPS epoch. A timestamp matching this interval will have to be the same or after the + * start. Required as a reference time for the initial correction value and its rate of + * change over time. + * @param endMillisSinceGpsEpoch Exclusive end of the interval in milliseconds since the GPS + * epoch. If specified, a timestamp matching this interval will have to be before the + * end. + */ + public GnssInterval( + @IntRange(from = 0) long startMillisSinceGpsEpoch, + @IntRange(from = 0) long endMillisSinceGpsEpoch) { + Preconditions.checkArgument(startMillisSinceGpsEpoch >= 0); + Preconditions.checkArgument(endMillisSinceGpsEpoch >= 0); + mStartMillisSinceGpsEpoch = startMillisSinceGpsEpoch; + mEndMillisSinceGpsEpoch = endMillisSinceGpsEpoch; + } + + /** Returns the inclusive start of the interval in milliseconds since the GPS epoch. */ + @IntRange(from = 0) + public long getStartMillisSinceGpsEpoch() { + return mStartMillisSinceGpsEpoch; + } + + /** Returns the exclusive end of the interval in milliseconds since the GPS epoch. */ + @IntRange(from = 0) + public long getEndMillisSinceGpsEpoch() { + return mEndMillisSinceGpsEpoch; + } + + public static final @NonNull Creator<GnssInterval> CREATOR = + new Creator<GnssInterval>() { + @Override + @NonNull + public GnssInterval createFromParcel(Parcel in) { + return new GnssInterval(in.readLong(), in.readLong()); + } + + @Override + public GnssInterval[] newArray(int size) { + return new GnssInterval[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeLong(mStartMillisSinceGpsEpoch); + parcel.writeLong(mEndMillisSinceGpsEpoch); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GnssInterval["); + builder.append("startMillisSinceGpsEpoch = ").append(mStartMillisSinceGpsEpoch); + builder.append(", endMillisSinceGpsEpoch = ").append(mEndMillisSinceGpsEpoch); + builder.append("]"); + return builder.toString(); + } + } + + /** Pseudorange correction. */ + public static final class PseudorangeCorrection implements Parcelable { + + /** Correction to be added to the measured pseudorange, in meters. */ + private final double mCorrectionMeters; + + /** Uncertainty of the correction, in meters. */ + private final double mCorrectionUncertaintyMeters; + + /** + * Linear approximation of the change in correction over time. Intended usage is to adjust + * the correction using the formula: correctionMeters + correctionRateMetersPerSecond * + * delta_seconds Where `delta_seconds` is the number of elapsed seconds since the beginning + * of the correction validity interval. + */ + private final double mCorrectionRateMetersPerSecond; + + /** + * Creates a PseudorangeCorrection. + * + * @param correctionMeters Correction to be added to the measured pseudorange, in meters. + * @param correctionUncertaintyMeters Uncertainty of the correction, in meters. + * @param correctionRateMetersPerSecond Linear approximation of the change in correction + * over time. Intended usage is to adjust the correction using the formula: + * correctionMeters + correctionRateMetersPerSecond * delta_seconds Where + * `delta_seconds` is the number of elapsed seconds since the beginning of the + * correction validity interval. + */ + public PseudorangeCorrection( + double correctionMeters, + double correctionUncertaintyMeters, + double correctionRateMetersPerSecond) { + Preconditions.checkArgument(correctionUncertaintyMeters >= 0); + mCorrectionMeters = correctionMeters; + mCorrectionUncertaintyMeters = correctionUncertaintyMeters; + mCorrectionRateMetersPerSecond = correctionRateMetersPerSecond; + } + + /** Returns the correction to be added to the measured pseudorange, in meters. */ + public double getCorrectionMeters() { + return mCorrectionMeters; + } + + /** Returns the uncertainty of the correction, in meters. */ + @FloatRange(from = 0.0f) + public double getCorrectionUncertaintyMeters() { + return mCorrectionUncertaintyMeters; + } + + /** Returns the linear approximation of the change in correction over time. */ + public double getCorrectionRateMetersPerSecond() { + return mCorrectionRateMetersPerSecond; + } + + public static final @NonNull Creator<PseudorangeCorrection> CREATOR = + new Creator<PseudorangeCorrection>() { + @Override + @NonNull + public PseudorangeCorrection createFromParcel(Parcel in) { + return new PseudorangeCorrection( + in.readDouble(), in.readDouble(), in.readDouble()); + } + + @Override + public PseudorangeCorrection[] newArray(int size) { + return new PseudorangeCorrection[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeDouble(mCorrectionMeters); + parcel.writeDouble(mCorrectionUncertaintyMeters); + parcel.writeDouble(mCorrectionRateMetersPerSecond); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("PseudorangeCorrection["); + builder.append("correctionMeters = ").append(mCorrectionMeters); + builder.append(", correctionUncertaintyMeters = ").append(mCorrectionUncertaintyMeters); + builder.append(", correctionRateMetersPerSecond = ") + .append(mCorrectionRateMetersPerSecond); + builder.append("]"); + return builder.toString(); + } + } +} diff --git a/location/java/android/location/GpsAssistance.java b/location/java/android/location/GpsAssistance.java new file mode 100644 index 000000000000..5202fc4cd851 --- /dev/null +++ b/location/java/android/location/GpsAssistance.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.location.GnssAssistance.GnssSatelliteCorrections; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains GPS assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GpsAssistance implements Parcelable { + + /** The GPS almanac. */ + @Nullable private final GnssAlmanac mAlmanac; + + /** The Klobuchar ionospheric model. */ + @Nullable private final KlobucharIonosphericModel mIonosphericModel; + + /** The UTC model. */ + @Nullable private final UtcModel mUtcModel; + + /** The leap seconds model. */ + @Nullable private final LeapSecondsModel mLeapSecondsModel; + + /** The list of time models. */ + @NonNull private final List<TimeModel> mTimeModels; + + /** The list of GPS ephemeris. */ + @NonNull private final List<GpsSatelliteEphemeris> mSatelliteEphemeris; + + /** The list of real time integrity models. */ + @NonNull private final List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + + /** The list of GPS satellite corrections. */ + @NonNull private final List<GnssSatelliteCorrections> mSatelliteCorrections; + + private GpsAssistance(Builder builder) { + mAlmanac = builder.mAlmanac; + mIonosphericModel = builder.mIonosphericModel; + mUtcModel = builder.mUtcModel; + mLeapSecondsModel = builder.mLeapSecondsModel; + if (builder.mTimeModels != null) { + mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels)); + } else { + mTimeModels = new ArrayList<>(); + } + if (builder.mSatelliteEphemeris != null) { + mSatelliteEphemeris = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteEphemeris)); + } else { + mSatelliteEphemeris = new ArrayList<>(); + } + if (builder.mRealTimeIntegrityModels != null) { + mRealTimeIntegrityModels = + Collections.unmodifiableList(new ArrayList<>(builder.mRealTimeIntegrityModels)); + } else { + mRealTimeIntegrityModels = new ArrayList<>(); + } + if (builder.mSatelliteCorrections != null) { + mSatelliteCorrections = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteCorrections)); + } else { + mSatelliteCorrections = new ArrayList<>(); + } + } + + /** Returns the GPS almanac. */ + @Nullable + public GnssAlmanac getAlmanac() { + return mAlmanac; + } + + /** Returns the Klobuchar ionospheric model. */ + @Nullable + public KlobucharIonosphericModel getIonosphericModel() { + return mIonosphericModel; + } + + /** Returns the UTC model. */ + @Nullable + public UtcModel getUtcModel() { + return mUtcModel; + } + + /** Returns the leap seconds model. */ + @Nullable + public LeapSecondsModel getLeapSecondsModel() { + return mLeapSecondsModel; + } + + /** Returns the list of time models. */ + @NonNull + public List<TimeModel> getTimeModels() { + return mTimeModels; + } + + /** Returns the list of GPS ephemeris. */ + @NonNull + public List<GpsSatelliteEphemeris> getSatelliteEphemeris() { + return mSatelliteEphemeris; + } + + /** Returns the list of real time integrity models. */ + @NonNull + public List<RealTimeIntegrityModel> getRealTimeIntegrityModels() { + return mRealTimeIntegrityModels; + } + + /** Returns the list of GPS satellite corrections. */ + @NonNull + public List<GnssSatelliteCorrections> getSatelliteCorrections() { + return mSatelliteCorrections; + } + + public static final @NonNull Creator<GpsAssistance> CREATOR = + new Creator<GpsAssistance>() { + @Override + @NonNull + public GpsAssistance createFromParcel(Parcel in) { + return new GpsAssistance.Builder() + .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR)) + .setIonosphericModel( + in.readTypedObject(KlobucharIonosphericModel.CREATOR)) + .setUtcModel(in.readTypedObject(UtcModel.CREATOR)) + .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR)) + .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR)) + .setSatelliteEphemeris( + in.createTypedArrayList(GpsSatelliteEphemeris.CREATOR)) + .setRealTimeIntegrityModels( + in.createTypedArrayList(RealTimeIntegrityModel.CREATOR)) + .setSatelliteCorrections( + in.createTypedArrayList(GnssSatelliteCorrections.CREATOR)) + .build(); + } + + @Override + public GpsAssistance[] newArray(int size) { + return new GpsAssistance[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mAlmanac, flags); + dest.writeTypedObject(mIonosphericModel, flags); + dest.writeTypedObject(mUtcModel, flags); + dest.writeTypedObject(mLeapSecondsModel, flags); + dest.writeTypedList(mTimeModels); + dest.writeTypedList(mSatelliteEphemeris); + dest.writeTypedList(mRealTimeIntegrityModels); + dest.writeTypedList(mSatelliteCorrections); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GnssAssistance["); + builder.append("almanac = ").append(mAlmanac); + builder.append(", ionosphericModel = ").append(mIonosphericModel); + builder.append(", utcModel = ").append(mUtcModel); + builder.append(", leapSecondsModel = ").append(mLeapSecondsModel); + builder.append(", timeModels = ").append(mTimeModels); + builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris); + builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels); + builder.append(", satelliteCorrections = ").append(mSatelliteCorrections); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GpsAssistance}. */ + public static final class Builder { + private GnssAlmanac mAlmanac; + private KlobucharIonosphericModel mIonosphericModel; + private UtcModel mUtcModel; + private LeapSecondsModel mLeapSecondsModel; + private List<TimeModel> mTimeModels; + private List<GpsSatelliteEphemeris> mSatelliteEphemeris; + private List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + private List<GnssSatelliteCorrections> mSatelliteCorrections; + + /** Sets the GPS almanac. */ + @NonNull + public Builder setAlmanac( + @Nullable @SuppressLint("NullableCollection") GnssAlmanac almanac) { + mAlmanac = almanac; + return this; + } + + /** Sets the Klobuchar ionospheric model. */ + @NonNull + public Builder setIonosphericModel( + @Nullable @SuppressLint("NullableCollection") + KlobucharIonosphericModel ionosphericModel) { + mIonosphericModel = ionosphericModel; + return this; + } + + /** Sets the UTC model. */ + @NonNull + public Builder setUtcModel( + @Nullable @SuppressLint("NullableCollection") UtcModel utcModel) { + mUtcModel = utcModel; + return this; + } + + /** Sets the leap seconds model. */ + @NonNull + public Builder setLeapSecondsModel( + @Nullable @SuppressLint("NullableCollection") LeapSecondsModel leapSecondsModel) { + mLeapSecondsModel = leapSecondsModel; + return this; + } + + /** Sets the list of time models. */ + @NonNull + public Builder setTimeModels( + @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) { + mTimeModels = timeModels; + return this; + } + + /** Sets the list of GPS ephemeris. */ + @NonNull + public Builder setSatelliteEphemeris( + @Nullable @SuppressLint("NullableCollection") + List<GpsSatelliteEphemeris> satelliteEphemeris) { + mSatelliteEphemeris = satelliteEphemeris; + return this; + } + + /** Sets the list of real time integrity models. */ + @NonNull + public Builder setRealTimeIntegrityModels( + @Nullable @SuppressLint("NullableCollection") + List<RealTimeIntegrityModel> realTimeIntegrityModels) { + mRealTimeIntegrityModels = realTimeIntegrityModels; + return this; + } + + /** Sets the list of GPS satellite corrections. */ + @NonNull + public Builder setSatelliteCorrections( + @Nullable @SuppressLint("NullableCollection") + List<GnssSatelliteCorrections> satelliteCorrections) { + mSatelliteCorrections = satelliteCorrections; + return this; + } + + /** Builds a {@link GpsAssistance} instance as specified by this builder. */ + @NonNull + public GpsAssistance build() { + return new GpsAssistance(this); + } + } +} diff --git a/location/java/android/location/GpsSatelliteEphemeris.java b/location/java/android/location/GpsSatelliteEphemeris.java new file mode 100644 index 000000000000..ec6bc59dc69c --- /dev/null +++ b/location/java/android/location/GpsSatelliteEphemeris.java @@ -0,0 +1,632 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * This class contains ephemeris parameters specific to GPS satellites. + * + * <p>This is defined in IS-GPS-200 section 20.3.3.3. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class GpsSatelliteEphemeris implements Parcelable { + /** Satellite PRN */ + private final int mPrn; + + /** L2 parameters. */ + @NonNull private final GpsL2Params mGpsL2Params; + + /** Clock model. */ + @NonNull private final GpsSatelliteClockModel mSatelliteClockModel; + + /** Orbit model. */ + @NonNull private final KeplerianOrbitModel mSatelliteOrbitModel; + + /** Satellite health. */ + @NonNull private final GpsSatelliteHealth mSatelliteHealth; + + /** Ephemeris time. */ + @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; + + private GpsSatelliteEphemeris(Builder builder) { + // Allow PRN beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mPrn >= 1); + Preconditions.checkNotNull(builder.mGpsL2Params, "GPSL2Params cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteClockModel, + "SatelliteClockModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteOrbitModel, + "SatelliteOrbitModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteHealth, + "SatelliteHealth cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, + "SatelliteEphemerisTime cannot be null"); + mPrn = builder.mPrn; + mGpsL2Params = builder.mGpsL2Params; + mSatelliteClockModel = builder.mSatelliteClockModel; + mSatelliteOrbitModel = builder.mSatelliteOrbitModel; + mSatelliteHealth = builder.mSatelliteHealth; + mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; + } + + /** Returns the PRN of the satellite. */ + @IntRange(from = 1, to = 32) + public int getPrn() { + return mPrn; + } + + /** Returns the L2 parameters of the satellite. */ + @NonNull + public GpsL2Params getGpsL2Params() { + return mGpsL2Params; + } + + /** Returns the clock model of the satellite. */ + @NonNull + public GpsSatelliteClockModel getSatelliteClockModel() { + return mSatelliteClockModel; + } + + /** Returns the orbit model of the satellite. */ + @NonNull + public KeplerianOrbitModel getSatelliteOrbitModel() { + return mSatelliteOrbitModel; + } + + /** Returns the satellite health. */ + @NonNull + public GpsSatelliteHealth getSatelliteHealth() { + return mSatelliteHealth; + } + + /** Returns the ephemeris time. */ + @NonNull + public SatelliteEphemerisTime getSatelliteEphemerisTime() { + return mSatelliteEphemerisTime; + } + + public static final @NonNull Creator<GpsSatelliteEphemeris> CREATOR = + new Creator<GpsSatelliteEphemeris>() { + @Override + @NonNull + public GpsSatelliteEphemeris createFromParcel(Parcel in) { + final GpsSatelliteEphemeris.Builder gpsSatelliteEphemeris = + new Builder() + .setPrn(in.readInt()) + .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) + .setSatelliteClockModel( + in.readTypedObject(GpsSatelliteClockModel.CREATOR)) + .setSatelliteOrbitModel( + in.readTypedObject(KeplerianOrbitModel.CREATOR)) + .setSatelliteHealth( + in.readTypedObject(GpsSatelliteHealth.CREATOR)) + .setSatelliteEphemerisTime( + in.readTypedObject(SatelliteEphemerisTime.CREATOR)); + return gpsSatelliteEphemeris.build(); + } + + @Override + public GpsSatelliteEphemeris[] newArray(int size) { + return new GpsSatelliteEphemeris[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mPrn); + parcel.writeTypedObject(mGpsL2Params, flags); + parcel.writeTypedObject(mSatelliteClockModel, flags); + parcel.writeTypedObject(mSatelliteOrbitModel, flags); + parcel.writeTypedObject(mSatelliteHealth, flags); + parcel.writeTypedObject(mSatelliteEphemerisTime, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GpsSatelliteEphemeris["); + builder.append("prn = ").append(mPrn); + builder.append(", gpsL2Params = ").append(mGpsL2Params); + builder.append(", satelliteClockModel = ").append(mSatelliteClockModel); + builder.append(", satelliteOrbitModel = ").append(mSatelliteOrbitModel); + builder.append(", satelliteHealth = ").append(mSatelliteHealth); + builder.append(", satelliteEphemerisTime = ").append(mSatelliteEphemerisTime); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GpsSatelliteEphemeris} */ + public static final class Builder { + private int mPrn = 0; + private GpsL2Params mGpsL2Params; + private GpsSatelliteClockModel mSatelliteClockModel; + private KeplerianOrbitModel mSatelliteOrbitModel; + private GpsSatelliteHealth mSatelliteHealth; + private SatelliteEphemerisTime mSatelliteEphemerisTime; + + /** Sets the PRN of the satellite. */ + @NonNull + public Builder setPrn(@IntRange(from = 1, to = 32) int prn) { + mPrn = prn; + return this; + } + + /** Sets the L2 parameters of the satellite. */ + @NonNull + public Builder setGpsL2Params(@NonNull GpsL2Params gpsL2Params) { + mGpsL2Params = gpsL2Params; + return this; + } + + /** Sets the clock model of the satellite. */ + @NonNull + public Builder setSatelliteClockModel(@NonNull GpsSatelliteClockModel satelliteClockModel) { + mSatelliteClockModel = satelliteClockModel; + return this; + } + + /** Sets the orbit model of the satellite. */ + @NonNull + public Builder setSatelliteOrbitModel(@NonNull KeplerianOrbitModel satelliteOrbitModel) { + mSatelliteOrbitModel = satelliteOrbitModel; + return this; + } + + /** Sets the satellite health. */ + @NonNull + public Builder setSatelliteHealth(@NonNull GpsSatelliteHealth satelliteHealth) { + mSatelliteHealth = satelliteHealth; + return this; + } + + /** Sets the ephemeris time. */ + @NonNull + public Builder setSatelliteEphemerisTime( + @NonNull SatelliteEphemerisTime satelliteEphemerisTime) { + mSatelliteEphemerisTime = satelliteEphemerisTime; + return this; + } + + /** Builds a {@link GpsSatelliteEphemeris} instance as specified by this builder. */ + @NonNull + public GpsSatelliteEphemeris build() { + return new GpsSatelliteEphemeris(this); + } + } + + /** + * A class contains information about GPS health. The information is tied to Legacy Navigation + * (LNAV) data, not Civil Navigation (CNAV) data. + */ + public static final class GpsSatelliteHealth implements Parcelable { + /** + * Represents "SV health" in the "BROADCAST ORBIT - 6" record of RINEX 3.05. Table A6, + * pp.68. + */ + private final int mSvHealth; + + /** + * Represents "SV accuracy" in meters in the "BROADCAST ORBIT - 6" record of RINEX 3.05. + * Table A6, pp.68. + */ + private final double mSvAccur; + + /** + * Represents the "Fit Interval" in hours in the "BROADCAST ORBIT - 7" record of RINEX 3.05. + * Table A6, pp.69. + */ + private final double mFitInt; + + /** Returns the SV health. */ + @IntRange(from = 0, to = 63) + public int getSvHealth() { + return mSvHealth; + } + + /** Returns the SV accuracy in meters. */ + @FloatRange(from = 0.0f, to = 8192.0f) + public double getSvAccur() { + return mSvAccur; + } + + /** Returns the fit interval in hours. */ + @FloatRange(from = 0.0f) + public double getFitInt() { + return mFitInt; + } + + private GpsSatelliteHealth(Builder builder) { + // Allow SV health beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvHealth >= 0); + Preconditions.checkArgumentInRange(builder.mSvAccur, 0.0f, 8192.0f, "SvAccur"); + Preconditions.checkArgument(builder.mFitInt >= 0.0f); + mSvHealth = builder.mSvHealth; + mSvAccur = builder.mSvAccur; + mFitInt = builder.mFitInt; + } + + public static final @NonNull Creator<GpsSatelliteHealth> CREATOR = + new Creator<GpsSatelliteHealth>() { + @Override + @NonNull + public GpsSatelliteHealth createFromParcel(Parcel in) { + final GpsSatelliteHealth.Builder gpsSatelliteHealth = + new Builder() + .setSvHealth(in.readInt()) + .setSvAccur(in.readDouble()) + .setFitInt(in.readDouble()); + return gpsSatelliteHealth.build(); + } + + @Override + public GpsSatelliteHealth[] newArray(int size) { + return new GpsSatelliteHealth[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSvHealth); + parcel.writeDouble(mSvAccur); + parcel.writeDouble(mFitInt); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GpsSatelliteHealth["); + builder.append("svHealth = ").append(mSvHealth); + builder.append(", svAccur = ").append(mSvAccur); + builder.append(", fitInt = ").append(mFitInt); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GpsSatelliteHealth}. */ + public static final class Builder { + private int mSvHealth; + private double mSvAccur; + private double mFitInt; + + /** Sets the SV health. */ + @NonNull + public Builder setSvHealth(@IntRange(from = 0, to = 63) int svHealth) { + mSvHealth = svHealth; + return this; + } + + /** Sets the SV accuracy in meters. */ + @NonNull + public Builder setSvAccur(@FloatRange(from = 0.0f, to = 8192.0f) double svAccur) { + mSvAccur = svAccur; + return this; + } + + /** Sets the fit interval in hours. */ + @NonNull + public Builder setFitInt(@FloatRange(from = 0.0f) double fitInt) { + mFitInt = fitInt; + return this; + } + + /** Builds a {@link GpsSatelliteHealth} instance as specified by this builder. */ + @NonNull + public GpsSatelliteHealth build() { + return new GpsSatelliteHealth(this); + } + } + } + + /** A class contains L2 parameters specific to GPS satellites. */ + public static final class GpsL2Params implements Parcelable { + /** Code(s) on L2 Channel. */ + private final int mL2Code; + + /** Data Flag for L2 P-Code. */ + private final int mL2Flag; + + /** Returns the code(s) on L2 channel. */ + @IntRange(from = 0, to = 3) + public int getL2Code() { + return mL2Code; + } + + /** Returns the data flag for L2 P-code. */ + @IntRange(from = 0, to = 1) + public int getL2Flag() { + return mL2Flag; + } + + private GpsL2Params(Builder builder) { + Preconditions.checkArgumentInRange(builder.mL2Code, 0, 3, "L2 code"); + Preconditions.checkArgumentInRange(builder.mL2Flag, 0, 1, "L2 flag"); + mL2Code = builder.mL2Code; + mL2Flag = builder.mL2Flag; + } + + public static final @NonNull Creator<GpsL2Params> CREATOR = + new Creator<GpsL2Params>() { + @Override + @NonNull + public GpsL2Params createFromParcel(Parcel in) { + final GpsL2Params.Builder gpsL2Params = + new Builder().setL2Code(in.readInt()).setL2Flag(in.readInt()); + return gpsL2Params.build(); + } + + @Override + public GpsL2Params[] newArray(int size) { + return new GpsL2Params[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mL2Code); + parcel.writeInt(mL2Flag); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GpsL2Params["); + builder.append("l2Code = ").append(mL2Code); + builder.append(", l2Flag = ").append(mL2Flag); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GpsL2Params}. */ + public static final class Builder { + private int mL2Code = 0; + private int mL2Flag = 0; + + /** Sets the code(s) on L2 channel. */ + @NonNull + public Builder setL2Code(@IntRange(from = 0, to = 3) int l2Code) { + mL2Code = l2Code; + return this; + } + + /** Sets the data flag for L2 P-code. */ + @NonNull + public Builder setL2Flag(@IntRange(from = 0, to = 1) int l2Flag) { + mL2Flag = l2Flag; + return this; + } + + /** Builds a {@link GpsL2Params} instance as specified by this builder. */ + @NonNull + public GpsL2Params build() { + return new GpsL2Params(this); + } + } + } + + /** A class contains the set of parameters needed for GPS satellite clock correction. */ + public static final class GpsSatelliteClockModel implements Parcelable { + /** + * Time of the clock in seconds since GPS epoch. + * + * <p>Corresponds to the 'Epoch' field within the 'SV/EPOCH/SV CLK' record of GPS + * navigation message in RINEX 3.05 Table A6. + */ + private final long mTimeOfClockSeconds; + + /** SV clock bias in seconds. */ + private final double mAf0; + + /** SV clock drift in seconds per second. */ + private final double mAf1; + + /** Clock drift rate in seconds per second squared. */ + private final double mAf2; + + /** Group delay differential in seconds. */ + private final double mTgd; + + /** Issue of data, clock. */ + private final int mIodc; + + private GpsSatelliteClockModel(Builder builder) { + Preconditions.checkArgument(builder.mTimeOfClockSeconds >= 0); + Preconditions.checkArgumentInRange(builder.mAf0, -9.77e-3f, 9.77e-3f, "Af0"); + Preconditions.checkArgumentInRange(builder.mAf1, -3.73e-9f, 3.73e-9f, "Af1"); + Preconditions.checkArgumentInRange(builder.mAf2, -3.56e-15f, 3.56e-15f, "Af2"); + Preconditions.checkArgumentInRange(builder.mTgd, -5.97e-8f, 5.97e-8f, "Tgd"); + Preconditions.checkArgumentInRange(builder.mIodc, 0, 1023, "Iodc"); + mTimeOfClockSeconds = builder.mTimeOfClockSeconds; + mAf0 = builder.mAf0; + mAf1 = builder.mAf1; + mAf2 = builder.mAf2; + mTgd = builder.mTgd; + mIodc = builder.mIodc; + } + + /** Returns the time of the clock in seconds since GPS epoch. */ + @IntRange(from = 0) + public long getTimeOfClockSeconds() { + return mTimeOfClockSeconds; + } + + /** Returns the SV clock bias in seconds. */ + @FloatRange(from = -9.77e-3f, to = 9.77e-3f) + public double getAf0() { + return mAf0; + } + + /** Returns the SV clock drift in seconds per second. */ + @FloatRange(from = -3.73e-9f, to = 3.73e-9f) + public double getAf1() { + return mAf1; + } + + /** Returns the clock drift rate in seconds per second squared. */ + @FloatRange(from = -3.56e-15f, to = 3.56e-15f) + public double getAf2() { + return mAf2; + } + + /** Returns the group delay differential in seconds. */ + @FloatRange(from = -5.97e-8f, to = 5.97e-8f) + public double getTgd() { + return mTgd; + } + + /** Returns the issue of data, clock. */ + @IntRange(from = 0, to = 1023) + public int getIodc() { + return mIodc; + } + + public static final @NonNull Creator<GpsSatelliteClockModel> CREATOR = + new Creator<GpsSatelliteClockModel>() { + @Override + @NonNull + public GpsSatelliteClockModel createFromParcel(Parcel in) { + final GpsSatelliteClockModel.Builder gpsSatelliteClockModel = + new Builder() + .setTimeOfClockSeconds(in.readLong()) + .setAf0(in.readDouble()) + .setAf1(in.readDouble()) + .setAf2(in.readDouble()) + .setTgd(in.readDouble()) + .setIodc(in.readInt()); + return gpsSatelliteClockModel.build(); + } + + @Override + public GpsSatelliteClockModel[] newArray(int size) { + return new GpsSatelliteClockModel[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeLong(mTimeOfClockSeconds); + parcel.writeDouble(mAf0); + parcel.writeDouble(mAf1); + parcel.writeDouble(mAf2); + parcel.writeDouble(mTgd); + parcel.writeInt(mIodc); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("GpsSatelliteClockModel["); + builder.append("timeOfClockSeconds = ").append(mTimeOfClockSeconds); + builder.append(", af0 = ").append(mAf0); + builder.append(", af1 = ").append(mAf1); + builder.append(", af2 = ").append(mAf2); + builder.append(", tgd = ").append(mTgd); + builder.append(", iodc = ").append(mIodc); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link GpsSatelliteClockModel}. */ + public static final class Builder { + private long mTimeOfClockSeconds; + private double mAf0; + private double mAf1; + private double mAf2; + private double mTgd; + private int mIodc; + + /** Sets the time of the clock in seconds since GPS epoch. */ + @NonNull + public Builder setTimeOfClockSeconds(@IntRange(from = 0) long timeOfClockSeconds) { + mTimeOfClockSeconds = timeOfClockSeconds; + return this; + } + + /** Sets the SV clock bias in seconds. */ + @NonNull + public Builder setAf0(@FloatRange(from = -9.77e-3f, to = 9.77e-3f) double af0) { + mAf0 = af0; + return this; + } + + /** Sets the SV clock drift in seconds per second. */ + @NonNull + public Builder setAf1(@FloatRange(from = -3.73e-9f, to = 3.73e-9f) double af1) { + mAf1 = af1; + return this; + } + + /** Sets the clock drift rate in seconds per second squared. */ + @NonNull + public Builder setAf2(@FloatRange(from = -3.56e-15f, to = 3.56e-15f) double af2) { + mAf2 = af2; + return this; + } + + /** Sets the group delay differential in seconds. */ + @NonNull + public Builder setTgd(@FloatRange(from = -5.97e-8f, to = 5.97e-8f) double tgd) { + mTgd = tgd; + return this; + } + + /** Sets the issue of data, clock. */ + @NonNull + public Builder setIodc(@IntRange(from = 0, to = 1023) int iodc) { + mIodc = iodc; + return this; + } + + /** Builds a {@link GpsSatelliteClockModel} instance as specified by this builder. */ + @NonNull + public GpsSatelliteClockModel build() { + return new GpsSatelliteClockModel(this); + } + } + } +} diff --git a/location/java/android/location/IonosphericCorrection.java b/location/java/android/location/IonosphericCorrection.java new file mode 100644 index 000000000000..aafcf8301add --- /dev/null +++ b/location/java/android/location/IonosphericCorrection.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains ionospheric correction. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class IonosphericCorrection implements Parcelable { + + /** Carrier frequency in Hz to differentiate signals from the same satellite. e.g. GPS L1/L5 */ + private final long mCarrierFrequencyHz; + + /** Ionospheric correction. */ + @NonNull private final GnssCorrectionComponent mIonosphericCorrection; + + /** + * Creates a new {@link IonosphericCorrection} instance. + * + * @param carrierFrequencyHz Carrier frequency in Hz to differentiate signals from the + * samesatellite. e.g. GPS L1/L5 + * @param ionosphericCorrection Ionospheric correction. + */ + public IonosphericCorrection( + @IntRange(from = 0) long carrierFrequencyHz, + @NonNull GnssCorrectionComponent ionosphericCorrection) { + Preconditions.checkArgument(carrierFrequencyHz > 0); + Preconditions.checkNotNull(ionosphericCorrection, "IonosphericCorrection cannot be null"); + mCarrierFrequencyHz = carrierFrequencyHz; + mIonosphericCorrection = ionosphericCorrection; + } + + /** + * Returns the carrier frequency in Hz to differentiate signals from the same satellite. e.g. + * GPS L1/L5 + */ + @IntRange(from = 0) + public long getCarrierFrequencyHz() { + return mCarrierFrequencyHz; + } + + /** Returns the Ionospheric correction. */ + @NonNull + public GnssCorrectionComponent getIonosphericCorrection() { + return mIonosphericCorrection; + } + + public static final @NonNull Creator<IonosphericCorrection> CREATOR = + new Creator<IonosphericCorrection>() { + @Override + @NonNull + public IonosphericCorrection createFromParcel(Parcel in) { + long carrierFrequencyHz = in.readLong(); + GnssCorrectionComponent ionosphericCorrection = + in.readTypedObject(GnssCorrectionComponent.CREATOR); + return new IonosphericCorrection(carrierFrequencyHz, ionosphericCorrection); + } + + @Override + public IonosphericCorrection[] newArray(int size) { + return new IonosphericCorrection[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mCarrierFrequencyHz); + dest.writeTypedObject(mIonosphericCorrection, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("IonosphericCorrection["); + builder.append("carrierFrequencyHz = ").append(mCarrierFrequencyHz); + builder.append(", ionosphericCorrection = ").append(mIonosphericCorrection); + builder.append("]"); + return builder.toString(); + } +} diff --git a/location/java/android/location/KeplerianOrbitModel.java b/location/java/android/location/KeplerianOrbitModel.java new file mode 100644 index 000000000000..a118274dc9a3 --- /dev/null +++ b/location/java/android/location/KeplerianOrbitModel.java @@ -0,0 +1,518 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Contains Keplerian orbit model parameters for GPS/Galileo/QZSS/Beidou. + * <p>For GPS, this is defined in IS-GPS-200 Table 20-II. + * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD-v2.1 section 5.1.1. + * <p>For QZSS, this is defined in IS-QZSS-PNT section 4.1.2. + * <p>For Baidou, this is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.12. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class KeplerianOrbitModel implements Parcelable { + /** Square root of the semi-major axis in square root of meters. */ + private final double mRootA; + + /** Eccentricity. */ + private final double mEccentricity; + + /** Inclination angle at reference time in radians. */ + private final double mI0; + + /** Rate of change of inclination angle in radians per second. */ + private final double mIDot; + + /** Argument of perigee in radians. */ + private final double mOmega; + + /** Longitude of ascending node of orbit plane at beginning of week in radians. */ + private final double mOmega0; + + /** Rate of right ascension in radians per second. */ + private final double mOmegaDot; + + /** Mean anomaly at reference time in radians. */ + private final double mM0; + + /** Mean motion difference from computed value in radians per second. */ + private final double mDeltaN; + + /** Second-order harmonic perturbations. */ + SecondOrderHarmonicPerturbation mSecondOrderHarmonicPerturbation; + + private KeplerianOrbitModel(Builder builder) { + Preconditions.checkArgumentInRange(builder.mRootA, 0.0f, 8192.0f, "RootA"); + Preconditions.checkArgumentInRange(builder.mEccentricity, 0.0f, 0.5f, "Eccentricity"); + Preconditions.checkArgumentInRange(builder.mI0, -3.15f, 3.15f, "I0"); + Preconditions.checkArgumentInRange(builder.mIDot, -2.94e-9f, 2.94e-9f, "IDot"); + Preconditions.checkArgumentInRange(builder.mOmega, -3.15f, 3.15f, "Omega"); + Preconditions.checkArgumentInRange(builder.mOmega0, -3.15f, 3.15f, "Omega0"); + Preconditions.checkArgumentInRange(builder.mOmegaDot, -3.1e-6f, 3.1e-6f, "OmegaDot"); + Preconditions.checkArgumentInRange(builder.mM0, -3.15f, 3.15f, "M0"); + Preconditions.checkArgumentInRange(builder.mDeltaN, -1.18e-8f, 1.18e-8f, "DeltaN"); + mRootA = builder.mRootA; + mEccentricity = builder.mEccentricity; + mI0 = builder.mI0; + mIDot = builder.mIDot; + mOmega = builder.mOmega; + mOmega0 = builder.mOmega0; + mOmegaDot = builder.mOmegaDot; + mM0 = builder.mM0; + mDeltaN = builder.mDeltaN; + mSecondOrderHarmonicPerturbation = builder.mSecondOrderHarmonicPerturbation; + } + + /** Get the square root of the semi-major axis in square root of meters. */ + @FloatRange(from = 0.0f, to = 8192.0f) + public double getRootA() { + return mRootA; + } + + /** Get the eccentricity. */ + @FloatRange(from = 0.0f, to = 0.5f) + public double getEccentricity() { + return mEccentricity; + } + + /** Get the inclination angle at reference time in radians. */ + @FloatRange(from = -3.15f, to = 3.15f) + public double getI0() { + return mI0; + } + + /** Get the rate of change of inclination angle in radians per second. */ + @FloatRange(from = -2.94e-9f, to = 2.94e-9f) + public double getIDot() { + return mIDot; + } + + /** Get the argument of perigee in radians. */ + @FloatRange(from = -3.15f, to = 3.15f) + public double getOmega() { + return mOmega; + } + + /** Get the longitude of ascending node of orbit plane at beginning of week in radians. */ + @FloatRange(from = -3.15f, to = 3.15f) + public double getOmega0() { + return mOmega0; + } + + /** Get the rate of right ascension in radians per second. */ + @FloatRange(from = -3.1e-6f, to = 3.1e-6f) + public double getOmegaDot() { + return mOmegaDot; + } + + /** Get the mean anomaly at reference time in radians. */ + @FloatRange(from = -3.15f, to = 3.15f) + public double getM0() { + return mM0; + } + + /** Get the mean motion difference from computed value in radians per second. */ + @FloatRange(from = -1.18e-8f, to = 1.18e-8f) + public double getDeltaN() { + return mDeltaN; + } + + /** Get the second-order harmonic perturbations. */ + @NonNull + public SecondOrderHarmonicPerturbation getSecondOrderHarmonicPerturbation() { + return mSecondOrderHarmonicPerturbation; + } + + public static final @NonNull Creator<KeplerianOrbitModel> CREATOR = + new Creator<KeplerianOrbitModel>() { + @Override + @NonNull + public KeplerianOrbitModel createFromParcel(Parcel in) { + final KeplerianOrbitModel.Builder keplerianOrbitModel = + new Builder() + .setRootA(in.readDouble()) + .setEccentricity(in.readDouble()) + .setI0(in.readDouble()) + .setIDot(in.readDouble()) + .setOmega(in.readDouble()) + .setOmega0(in.readDouble()) + .setOmegaDot(in.readDouble()) + .setM0(in.readDouble()) + .setDeltaN(in.readDouble()) + .setSecondOrderHarmonicPerturbation( + in.readTypedObject( + SecondOrderHarmonicPerturbation.CREATOR)); + return keplerianOrbitModel.build(); + } + + @Override + public KeplerianOrbitModel[] newArray(int size) { + return new KeplerianOrbitModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeDouble(mRootA); + parcel.writeDouble(mEccentricity); + parcel.writeDouble(mI0); + parcel.writeDouble(mIDot); + parcel.writeDouble(mOmega); + parcel.writeDouble(mOmega0); + parcel.writeDouble(mOmegaDot); + parcel.writeDouble(mM0); + parcel.writeDouble(mDeltaN); + parcel.writeTypedObject(mSecondOrderHarmonicPerturbation, flags); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("KeplerianOrbitModel["); + builder.append("rootA = ").append(mRootA); + builder.append(", eccentricity = ").append(mEccentricity); + builder.append(", i0 = ").append(mI0); + builder.append(", iDot = ").append(mIDot); + builder.append(", omega = ").append(mOmega); + builder.append(", omega0 = ").append(mOmega0); + builder.append(", omegaDot = ").append(mOmegaDot); + builder.append(", m0 = ").append(mM0); + builder.append(", deltaN = ").append(mDeltaN); + builder.append(", secondOrderHarmonicPerturbation = ") + .append(mSecondOrderHarmonicPerturbation); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link KeplerianOrbitModel} */ + public static final class Builder { + private double mRootA; + private double mEccentricity; + private double mI0; + private double mIDot; + private double mOmega; + private double mOmega0; + private double mOmegaDot; + private double mM0; + private double mDeltaN; + private SecondOrderHarmonicPerturbation mSecondOrderHarmonicPerturbation; + + /** Sets the square root of the semi-major axis in square root of meters. */ + @NonNull + public Builder setRootA(@FloatRange(from = 0.0f, to = 8192.0f) double rootA) { + mRootA = rootA; + return this; + } + + /** Sets the eccentricity. */ + @NonNull + public Builder setEccentricity(@FloatRange(from = 0.0f, to = 0.5f) double eccentricity) { + mEccentricity = eccentricity; + return this; + } + + /** Sets the inclination angle at reference time in radians. */ + @NonNull + public Builder setI0(@FloatRange(from = -3.15f, to = 3.15f) double i0) { + mI0 = i0; + return this; + } + + /** Sets the rate of change of inclination angle in radians per second. */ + @NonNull + public Builder setIDot(@FloatRange(from = -2.94e-9f, to = 2.94e-9f) double iDot) { + mIDot = iDot; + return this; + } + + /** Sets the argument of perigee in radians. */ + @NonNull + public Builder setOmega(@FloatRange(from = -3.15f, to = 3.15f) double omega) { + mOmega = omega; + return this; + } + + /** + * Sets the longitude of ascending node of orbit plane at beginning of week in radians. + */ + @NonNull + public Builder setOmega0(@FloatRange(from = -3.15f, to = 3.15f) double omega0) { + mOmega0 = omega0; + return this; + } + + /** Sets the rate of right ascension in radians per second. */ + @NonNull + public Builder setOmegaDot(@FloatRange(from = -3.1e-6f, to = 3.1e-6f) double omegaDot) { + mOmegaDot = omegaDot; + return this; + } + + /** Sets the mean anomaly at reference time in radians. */ + @NonNull + public Builder setM0(@FloatRange(from = -3.15f, to = 3.15f) double m0) { + mM0 = m0; + return this; + } + + /** Sets the mean motion difference from computed value in radians per second. */ + @NonNull + public Builder setDeltaN(@FloatRange(from = -1.18e-8f, to = 1.18e-8f) double deltaN) { + mDeltaN = deltaN; + return this; + } + + /** Sets the second-order harmonic perturbations. */ + @NonNull + public Builder setSecondOrderHarmonicPerturbation( + @NonNull SecondOrderHarmonicPerturbation secondOrderHarmonicPerturbation) { + mSecondOrderHarmonicPerturbation = secondOrderHarmonicPerturbation; + return this; + } + + /** Builds a {@link KeplerianOrbitModel} instance as specified by this builder. */ + @NonNull + public KeplerianOrbitModel build() { + return new KeplerianOrbitModel(this); + } + } + + /** A class contains second-order harmonic perturbations. */ + public static final class SecondOrderHarmonicPerturbation implements Parcelable { + /** Amplitude of cosine harmonic correction term to angle of inclination in radians. */ + private final double mCic; + + /** Amplitude of sine harmonic correction term to angle of inclination in radians. */ + private final double mCis; + + /** Amplitude of cosine harmonic correction term to the orbit in meters. */ + private final double mCrc; + + /** Amplitude of sine harmonic correction term to the orbit in meters. */ + private final double mCrs; + + /** Amplitude of cosine harmonic correction term to the argument of latitude in radians. */ + private final double mCuc; + + /** Amplitude of sine harmonic correction term to the argument of latitude in radians. */ + private final double mCus; + + private SecondOrderHarmonicPerturbation(Builder builder) { + Preconditions.checkArgumentInRange(builder.mCic, -6.11e-5f, 6.11e-5f, "Cic"); + Preconditions.checkArgumentInRange(builder.mCis, -6.11e-5f, 6.11e-5f, "Cis"); + Preconditions.checkArgumentInRange(builder.mCrc, -2048.0f, 2048.0f, "Crc"); + Preconditions.checkArgumentInRange(builder.mCrs, -2048.0f, 2048.0f, "Crs"); + Preconditions.checkArgumentInRange(builder.mCuc, -6.11e-5f, 6.11e-5f, "Cuc"); + Preconditions.checkArgumentInRange(builder.mCus, -6.11e-5f, 6.11e-5f, "Cus"); + mCic = builder.mCic; + mCrc = builder.mCrc; + mCis = builder.mCis; + mCrs = builder.mCrs; + mCuc = builder.mCuc; + mCus = builder.mCus; + } + + /** + * Get the amplitude of cosine harmonic correction term to angle of inclination in radians. + */ + @FloatRange(from = -6.11e-5f, to = 6.11e-5f) + public double getCic() { + return mCic; + } + + /** + * Get the amplitude of sine harmonic correction term to angle of inclination in radians. + */ + @FloatRange(from = -6.11e-5f, to = 6.11e-5f) + public double getCis() { + return mCis; + } + + /** Get the amplitude of cosine harmonic correction term to the orbit in meters. */ + @FloatRange(from = -2048.0f, to = 2048.0f) + public double getCrc() { + return mCrc; + } + + /** Get the amplitude of sine harmonic correction term to the orbit in meters. */ + @FloatRange(from = -2048.0f, to = 2048.0f) + public double getCrs() { + return mCrs; + } + + /** + * Get the amplitude of cosine harmonic correction term to the argument of latitude in + * radians. + */ + @FloatRange(from = -6.11e-5f, to = 6.11e-5f) + public double getCuc() { + return mCuc; + } + + /** + * Get the amplitude of sine harmonic correction term to the argument of latitude in + * radians. + */ + @FloatRange(from = -6.11e-5f, to = 6.11e-5f) + public double getCus() { + return mCus; + } + + public static final @NonNull Creator<SecondOrderHarmonicPerturbation> CREATOR = + new Creator<SecondOrderHarmonicPerturbation>() { + @Override + @NonNull + public SecondOrderHarmonicPerturbation createFromParcel(Parcel in) { + final SecondOrderHarmonicPerturbation.Builder + secondOrderHarmonicPerturbation = + new Builder() + .setCic(in.readDouble()) + .setCis(in.readDouble()) + .setCrc(in.readDouble()) + .setCrs(in.readDouble()) + .setCuc(in.readDouble()) + .setCus(in.readDouble()); + return secondOrderHarmonicPerturbation.build(); + } + + @Override + public SecondOrderHarmonicPerturbation[] newArray(int size) { + return new SecondOrderHarmonicPerturbation[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeDouble(mCic); + parcel.writeDouble(mCis); + parcel.writeDouble(mCrc); + parcel.writeDouble(mCrs); + parcel.writeDouble(mCuc); + parcel.writeDouble(mCus); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("SecondOrderHarmonicPerturbation["); + builder.append("cic = ").append(mCic); + builder.append(", cis = ").append(mCis); + builder.append(", crc = ").append(mCrc); + builder.append(", crs = ").append(mCrs); + builder.append(", cuc = ").append(mCuc); + builder.append(", cus = ").append(mCus); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link SecondOrderHarmonicPerturbation} */ + public static final class Builder { + private double mCic; + private double mCis; + private double mCrc; + private double mCrs; + private double mCuc; + private double mCus; + + /** + * Sets the amplitude of cosine harmonic correction term to angle of inclination in + * radians. + */ + @NonNull + public Builder setCic(@FloatRange(from = -6.11e-5f, to = 6.11e-5f) double cic) { + mCic = cic; + return this; + } + + /** + * Sets the amplitude of sine harmonic correction term to angle of inclination in + * radians. + */ + @NonNull + public Builder setCis(@FloatRange(from = -6.11e-5f, to = 6.11e-5f) double cis) { + mCis = cis; + return this; + } + + /** Sets the amplitude of cosine harmonic correction term to the orbit in meters. */ + @NonNull + public Builder setCrc(@FloatRange(from = -2048.0f, to = 2048.0f) double crc) { + mCrc = crc; + return this; + } + + /** Sets the amplitude of sine harmonic correction term to the orbit in meters. */ + @NonNull + public Builder setCrs(@FloatRange(from = -2048.0f, to = 2048.0f) double crs) { + mCrs = crs; + return this; + } + + /** + * Sets the amplitude of cosine harmonic correction term to the argument of latitude in + * radians. + */ + @NonNull + public Builder setCuc(@FloatRange(from = -6.11e-5f, to = 6.11e-5f) double cuc) { + mCuc = cuc; + return this; + } + + /** + * Sets the amplitude of sine harmonic correction term to the argument of latitude in + * radians. + */ + @NonNull + public Builder setCus(@FloatRange(from = -6.11e-5f, to = 6.11e-5f) double cus) { + mCus = cus; + return this; + } + + /** + * Builds a {@link SecondOrderHarmonicPerturbation} instance as specified by this + * builder. + */ + @NonNull + public SecondOrderHarmonicPerturbation build() { + return new SecondOrderHarmonicPerturbation(this); + } + } + } +} diff --git a/location/java/android/location/KlobucharIonosphericModel.java b/location/java/android/location/KlobucharIonosphericModel.java new file mode 100644 index 000000000000..d239c876f702 --- /dev/null +++ b/location/java/android/location/KlobucharIonosphericModel.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains Klobuchar ionospheric model coefficients used by GPS, BDS, QZSS. + * + * <p>This is defined in IS-GPS-200 section 20.3.3.5.1.7. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class KlobucharIonosphericModel implements Parcelable { + /** Alpha0 coefficientin seconds. */ + double mAlpha0; + /** Alpha1 coefficient in seconds per semi-circle. */ + double mAlpha1; + /** Alpha2 coefficient in seconds per semi-circle squared. */ + double mAlpha2; + /** Alpha3 coefficient in seconds per semi-circle cubed. */ + double mAlpha3; + /** Beta0 coefficient in seconds. */ + double mBeta0; + /** Beta1 coefficient in seconds per semi-circle. */ + double mBeta1; + /** Beta2 coefficient in seconds per semi-circle squared. */ + double mBeta2; + /** Beta3 coefficient in seconds per semi-circle cubed. */ + double mBeta3; + + private KlobucharIonosphericModel(Builder builder) { + Preconditions.checkArgumentInRange(builder.mAlpha0, -1.193e-7f, 1.193e-7f, "Alpha0"); + Preconditions.checkArgumentInRange(builder.mAlpha1, -9.54e-7f, 9.54e-7f, "Alpha1"); + Preconditions.checkArgumentInRange(builder.mAlpha2, -7.63e-6f, 7.63e-6f, "Alpha2"); + Preconditions.checkArgumentInRange(builder.mAlpha3, -7.63e-6f, 7.63e-6f, "Alpha3"); + Preconditions.checkArgumentInRange(builder.mBeta0, -262144.0f, 262144.0f, "Beta0"); + Preconditions.checkArgumentInRange(builder.mBeta1, -2097152.0f, 2097152.0f, "Beta1"); + Preconditions.checkArgumentInRange(builder.mBeta2, -8388608.0f, 8388608.0f, "Beta2"); + Preconditions.checkArgumentInRange(builder.mBeta3, -8388608.0f, 8388608.0f, "Beta3"); + mAlpha0 = builder.mAlpha0; + mAlpha1 = builder.mAlpha1; + mAlpha2 = builder.mAlpha2; + mAlpha3 = builder.mAlpha3; + mBeta0 = builder.mBeta0; + mBeta1 = builder.mBeta1; + mBeta2 = builder.mBeta2; + mBeta3 = builder.mBeta3; + } + + /** Returns the alpha0 coefficient in seconds. */ + @FloatRange(from = -1.193e-7f, to = 1.193e-7f) + public double getAlpha0() { + return mAlpha0; + } + + /** Returns the alpha1 coefficient in seconds per semi-circle. */ + @FloatRange(from = -9.54e-7f, to = 9.54e-7f) + public double getAlpha1() { + return mAlpha1; + } + + /** Returns the alpha2 coefficient in seconds per semi-circle squared. */ + @FloatRange(from = -7.63e-6f, to = 7.63e-6f) + public double getAlpha2() { + return mAlpha2; + } + + /** Returns the alpha3 coefficient in seconds per semi-circle cubed. */ + @FloatRange(from = -7.63e-6f, to = 7.63e-6f) + public double getAlpha3() { + return mAlpha3; + } + + /** Returns the beta0 coefficient in seconds. */ + @FloatRange(from = -262144.0f, to = 262144.0f) + public double getBeta0() { + return mBeta0; + } + + /** Returns the beta1 coefficient in seconds per semi-circle. */ + @FloatRange(from = -2097152.0f, to = 2097152.0f) + public double getBeta1() { + return mBeta1; + } + + /** Returns the beta2 coefficient in seconds per semi-circle squared. */ + @FloatRange(from = -8388608.0f, to = 8388608.0f) + public double getBeta2() { + return mBeta2; + } + + /** Returns the beta3 coefficient in seconds per semi-circle cubed. */ + @FloatRange(from = -8388608.0f, to = 8388608.0f) + public double getBeta3() { + return mBeta3; + } + + public static final @NonNull Creator<KlobucharIonosphericModel> CREATOR = + new Creator<KlobucharIonosphericModel>() { + @Override + @NonNull + public KlobucharIonosphericModel createFromParcel(Parcel in) { + return new KlobucharIonosphericModel.Builder() + .setAlpha0(in.readDouble()) + .setAlpha1(in.readDouble()) + .setAlpha2(in.readDouble()) + .setAlpha3(in.readDouble()) + .setBeta0(in.readDouble()) + .setBeta1(in.readDouble()) + .setBeta2(in.readDouble()) + .setBeta3(in.readDouble()) + .build(); + } + @Override + public KlobucharIonosphericModel[] newArray(int size) { + return new KlobucharIonosphericModel[size]; + } + }; + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeDouble(mAlpha0); + parcel.writeDouble(mAlpha1); + parcel.writeDouble(mAlpha2); + parcel.writeDouble(mAlpha3); + parcel.writeDouble(mBeta0); + parcel.writeDouble(mBeta1); + parcel.writeDouble(mBeta2); + parcel.writeDouble(mBeta3); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("KlobucharIonosphericModel["); + builder.append("alpha0 = ").append(mAlpha0); + builder.append(", alpha1 = ").append(mAlpha1); + builder.append(", alpha2 = ").append(mAlpha2); + builder.append(", alpha3 = ").append(mAlpha3); + builder.append(", beta0 = ").append(mBeta0); + builder.append(", beta1 = ").append(mBeta1); + builder.append(", beta2 = ").append(mBeta2); + builder.append(", beta3 = ").append(mBeta3); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link KlobucharIonosphericModel} */ + public static final class Builder { + private double mAlpha0; + private double mAlpha1; + private double mAlpha2; + private double mAlpha3; + private double mBeta0; + private double mBeta1; + private double mBeta2; + private double mBeta3; + + /** Sets the alpha0 coefficient in seconds. */ + @NonNull + public Builder setAlpha0(@FloatRange(from = -1.193e-7f, to = 1.193e-7f) double alpha0) { + mAlpha0 = alpha0; + return this; + } + + /** Sets the alpha1 coefficient in seconds per semi-circle. */ + @NonNull + public Builder setAlpha1(@FloatRange(from = -9.54e-7f, to = 9.54e-7f) double alpha1) { + mAlpha1 = alpha1; + return this; + } + + /** Sets the alpha2 coefficient in seconds per semi-circle squared. */ + @NonNull + public Builder setAlpha2(@FloatRange(from = -7.63e-6f, to = 7.63e-6f) double alpha2) { + mAlpha2 = alpha2; + return this; + } + + /** Sets the alpha3 coefficient in seconds per semi-circle cubed. */ + @NonNull + public Builder setAlpha3(@FloatRange(from = -7.63e-6f, to = 7.63e-6f) double alpha3) { + mAlpha3 = alpha3; + return this; + } + + /** Sets the beta0 coefficient in seconds. */ + @NonNull + public Builder setBeta0(@FloatRange(from = -262144.0f, to = 262144.0f) double beta0) { + mBeta0 = beta0; + return this; + } + + /** Sets the beta1 coefficient in seconds per semi-circle. */ + @NonNull + public Builder setBeta1(@FloatRange(from = -2097152.0f, to = 2097152.0f) double beta1) { + mBeta1 = beta1; + return this; + } + + /** Sets the beta2 coefficient in seconds per semi-circle squared. */ + @NonNull + public Builder setBeta2(@FloatRange(from = -8388608.0f, to = 8388608.0f) double beta2) { + mBeta2 = beta2; + return this; + } + + /** Sets the beta3 coefficient in seconds per semi-circle cubed. */ + @NonNull + public Builder setBeta3(@FloatRange(from = -8388608.0f, to = 8388608.0f) double beta3) { + mBeta3 = beta3; + return this; + } + + /** Builds a {@link KlobucharIonosphericModel} instance as specified by this builder. */ + @NonNull + public KlobucharIonosphericModel build() { + return new KlobucharIonosphericModel(this); + } + } +} diff --git a/location/java/android/location/LeapSecondsModel.java b/location/java/android/location/LeapSecondsModel.java new file mode 100644 index 000000000000..bc132addb06a --- /dev/null +++ b/location/java/android/location/LeapSecondsModel.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Contains the leap seconds set of parameters needed for GNSS time. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class LeapSecondsModel implements Parcelable { + /** Time difference due to leap seconds before the event in seconds. (UTC) */ + private final int mLeapSeconds; + + /** Time difference due to leap seconds after the event in seconds. (UTC) */ + private final int mLeapSecondsFuture; + + /** GNSS week number in which the leap second event will occur. (UTC) */ + private final int mWeekNumberLeapSecondsFuture; + + /** Day number when the next leap second will occur. */ + private final int mDayNumberLeapSecondsFuture; + + private LeapSecondsModel(Builder builder) { + Preconditions.checkArgument(builder.mLeapSeconds >= 0); + Preconditions.checkArgument(builder.mLeapSecondsFuture >= 0); + Preconditions.checkArgument(builder.mWeekNumberLeapSecondsFuture >= 0); + Preconditions.checkArgument(builder.mDayNumberLeapSecondsFuture >= 0); + mLeapSeconds = builder.mLeapSeconds; + mLeapSecondsFuture = builder.mLeapSecondsFuture; + mWeekNumberLeapSecondsFuture = builder.mWeekNumberLeapSecondsFuture; + mDayNumberLeapSecondsFuture = builder.mDayNumberLeapSecondsFuture; + } + + /** Returns the time difference due to leap seconds before the event in seconds. (UTC) */ + @IntRange(from = 0) + public int getLeapSeconds() { + return mLeapSeconds; + } + + /** Returns the time difference due to leap seconds after the event in seconds. (UTC) */ + @IntRange(from = 0) + public int getLeapSecondsFuture() { + return mLeapSecondsFuture; + } + + /** Returns the GNSS week number in which the leap second event will occur. (UTC) */ + @IntRange(from = 0) + public int getWeekNumberLeapSecondsFuture() { + return mWeekNumberLeapSecondsFuture; + } + + /** Returns the day number when the next leap second will occur. */ + @IntRange(from = 0) + public int getDayNumberLeapSecondsFuture() { + return mDayNumberLeapSecondsFuture; + } + + public static final @NonNull Creator<LeapSecondsModel> CREATOR = + new Creator<LeapSecondsModel>() { + @Override + @NonNull + public LeapSecondsModel createFromParcel(Parcel in) { + final LeapSecondsModel.Builder leapSecondsModel = new Builder(); + leapSecondsModel.setLeapSeconds(in.readInt()); + leapSecondsModel.setLeapSecondsFuture(in.readInt()); + leapSecondsModel.setWeekNumberLeapSecondsFuture(in.readInt()); + leapSecondsModel.setDayNumberLeapSecondsFuture(in.readInt()); + return leapSecondsModel.build(); + } + + @Override + public LeapSecondsModel[] newArray(int size) { + return new LeapSecondsModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mLeapSeconds); + parcel.writeInt(mLeapSecondsFuture); + parcel.writeInt(mWeekNumberLeapSecondsFuture); + parcel.writeInt(mDayNumberLeapSecondsFuture); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("LeapSecondsModel["); + builder.append("leapSeconds = ").append(mLeapSeconds); + builder.append(", leapSecondsFuture = ").append(mLeapSecondsFuture); + builder.append(", weekNumberLeapSecondsFuture = ").append(mWeekNumberLeapSecondsFuture); + builder.append(", dayNumberLeapSecondsFuture = ").append(mDayNumberLeapSecondsFuture); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link LeapSecondsModel} */ + public static final class Builder { + private int mLeapSeconds; + private int mLeapSecondsFuture; + private int mWeekNumberLeapSecondsFuture; + private int mDayNumberLeapSecondsFuture; + + /** Sets the time difference due to leap seconds before the event in seconds. (UTC) */ + @NonNull + public Builder setLeapSeconds(@IntRange(from = 0) int leapSeconds) { + mLeapSeconds = leapSeconds; + return this; + } + + /** Sets the time difference due to leap seconds after the event in seconds. (UTC) */ + @NonNull + public Builder setLeapSecondsFuture(@IntRange(from = 0) int leapSecondsFuture) { + mLeapSecondsFuture = leapSecondsFuture; + return this; + } + + /** Sets the GNSS week number in which the leap second event will occur. (UTC) */ + @NonNull + public Builder setWeekNumberLeapSecondsFuture( + @IntRange(from = 0) int weekNumberLeapSecondsFuture) { + mWeekNumberLeapSecondsFuture = weekNumberLeapSecondsFuture; + return this; + } + + /** Sets the day number when the next leap second will occur. */ + @NonNull + public Builder setDayNumberLeapSecondsFuture( + @IntRange(from = 0) int dayNumberLeapSecondsFuture) { + mDayNumberLeapSecondsFuture = dayNumberLeapSecondsFuture; + return this; + } + + /** Builds a {@link LeapSecondsModel} instance as specified by this builder. */ + @NonNull + public LeapSecondsModel build() { + return new LeapSecondsModel(this); + } + } +} diff --git a/location/java/android/location/QzssAssistance.java b/location/java/android/location/QzssAssistance.java new file mode 100644 index 000000000000..9383ce3c63b5 --- /dev/null +++ b/location/java/android/location/QzssAssistance.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.location.GnssAssistance.GnssSatelliteCorrections; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A class contains QZSS assistance. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class QzssAssistance implements Parcelable { + + /** The QZSS almanac. */ + @Nullable private final GnssAlmanac mAlmanac; + + /** The Klobuchar ionospheric model. */ + @Nullable private final KlobucharIonosphericModel mIonosphericModel; + + /** The UTC model. */ + @Nullable private final UtcModel mUtcModel; + + /** The leap seconds model. */ + @Nullable private final LeapSecondsModel mLeapSecondsModel; + + /** The list of time models. */ + @NonNull private final List<TimeModel> mTimeModels; + + /** The list of QZSS ephemeris. */ + @NonNull private final List<QzssSatelliteEphemeris> mSatelliteEphemeris; + + /** The list of real time integrity models. */ + @NonNull private final List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + + /** The list of QZSS satellite corrections. */ + @NonNull private final List<GnssSatelliteCorrections> mSatelliteCorrections; + + private QzssAssistance(Builder builder) { + mAlmanac = builder.mAlmanac; + mIonosphericModel = builder.mIonosphericModel; + mUtcModel = builder.mUtcModel; + mLeapSecondsModel = builder.mLeapSecondsModel; + if (builder.mTimeModels != null) { + mTimeModels = Collections.unmodifiableList(new ArrayList<>(builder.mTimeModels)); + } else { + mTimeModels = new ArrayList<>(); + } + if (builder.mSatelliteEphemeris != null) { + mSatelliteEphemeris = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteEphemeris)); + } else { + mSatelliteEphemeris = new ArrayList<>(); + } + if (builder.mRealTimeIntegrityModels != null) { + mRealTimeIntegrityModels = + Collections.unmodifiableList(new ArrayList<>(builder.mRealTimeIntegrityModels)); + } else { + mRealTimeIntegrityModels = new ArrayList<>(); + } + if (builder.mSatelliteCorrections != null) { + mSatelliteCorrections = + Collections.unmodifiableList(new ArrayList<>(builder.mSatelliteCorrections)); + } else { + mSatelliteCorrections = new ArrayList<>(); + } + } + + /** Returns the QZSS almanac. */ + @Nullable + public GnssAlmanac getAlmanac() { + return mAlmanac; + } + + /** Returns the Klobuchar ionospheric model. */ + @Nullable + public KlobucharIonosphericModel getIonosphericModel() { + return mIonosphericModel; + } + + /** Returns the UTC model. */ + @Nullable + public UtcModel getUtcModel() { + return mUtcModel; + } + + /** Returns the leap seconds model. */ + @Nullable + public LeapSecondsModel getLeapSecondsModel() { + return mLeapSecondsModel; + } + + /** Returns the list of time models. */ + @NonNull + public List<TimeModel> getTimeModels() { + return mTimeModels; + } + + /** Returns the list of QZSS ephemeris. */ + @NonNull + public List<QzssSatelliteEphemeris> getSatelliteEphemeris() { + return mSatelliteEphemeris; + } + + /** Returns the list of real time integrity models. */ + @NonNull + public List<RealTimeIntegrityModel> getRealTimeIntegrityModels() { + return mRealTimeIntegrityModels; + } + + /** Returns the list of QZSS satellite corrections. */ + @NonNull + public List<GnssSatelliteCorrections> getSatelliteCorrections() { + return mSatelliteCorrections; + } + + public static final @NonNull Creator<QzssAssistance> CREATOR = + new Creator<QzssAssistance>() { + @Override + @NonNull + public QzssAssistance createFromParcel(Parcel in) { + return new QzssAssistance.Builder() + .setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR)) + .setIonosphericModel(in.readTypedObject(KlobucharIonosphericModel.CREATOR)) + .setUtcModel(in.readTypedObject(UtcModel.CREATOR)) + .setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR)) + .setTimeModels(in.createTypedArrayList(TimeModel.CREATOR)) + .setSatelliteEphemeris( + in.createTypedArrayList(QzssSatelliteEphemeris.CREATOR)) + .setRealTimeIntegrityModels( + in.createTypedArrayList(RealTimeIntegrityModel.CREATOR)) + .setSatelliteCorrections( + in.createTypedArrayList(GnssSatelliteCorrections.CREATOR)) + .build(); + } + @Override + public QzssAssistance[] newArray(int size) { + return new QzssAssistance[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mAlmanac, flags); + dest.writeTypedObject(mIonosphericModel, flags); + dest.writeTypedObject(mUtcModel, flags); + dest.writeTypedObject(mLeapSecondsModel, flags); + dest.writeTypedList(mTimeModels); + dest.writeTypedList(mSatelliteEphemeris); + dest.writeTypedList(mRealTimeIntegrityModels); + dest.writeTypedList(mSatelliteCorrections); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("QzssAssistance["); + builder.append("almanac = ").append(mAlmanac); + builder.append(", ionosphericModel = ").append(mIonosphericModel); + builder.append(", utcModel = ").append(mUtcModel); + builder.append(", leapSecondsModel = ").append(mLeapSecondsModel); + builder.append(", timeModels = ").append(mTimeModels); + builder.append(", satelliteEphemeris = ").append(mSatelliteEphemeris); + builder.append(", realTimeIntegrityModels = ").append(mRealTimeIntegrityModels); + builder.append(", satelliteCorrections = ").append(mSatelliteCorrections); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link QzssAssistance}. */ + public static final class Builder { + private GnssAlmanac mAlmanac; + private KlobucharIonosphericModel mIonosphericModel; + private UtcModel mUtcModel; + private LeapSecondsModel mLeapSecondsModel; + private List<TimeModel> mTimeModels; + private List<QzssSatelliteEphemeris> mSatelliteEphemeris; + private List<RealTimeIntegrityModel> mRealTimeIntegrityModels; + private List<GnssSatelliteCorrections> mSatelliteCorrections; + + /** Sets the QZSS almanac. */ + @NonNull + public Builder setAlmanac(@Nullable GnssAlmanac almanac) { + mAlmanac = almanac; + return this; + } + + /** Sets the Klobuchar ionospheric model. */ + @NonNull + public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) { + mIonosphericModel = ionosphericModel; + return this; + } + + /** Sets the UTC model. */ + @NonNull + public Builder setUtcModel(@Nullable UtcModel utcModel) { + mUtcModel = utcModel; + return this; + } + + /** Sets the leap seconds model. */ + @NonNull + public Builder setLeapSecondsModel(@Nullable LeapSecondsModel leapSecondsModel) { + mLeapSecondsModel = leapSecondsModel; + return this; + } + + /** Sets the list of time models. */ + @NonNull + public Builder setTimeModels( + @Nullable @SuppressLint("NullableCollection") List<TimeModel> timeModels) { + mTimeModels = timeModels; + return this; + } + + /** Sets the list of QZSS ephemeris. */ + @NonNull + public Builder setSatelliteEphemeris( + @Nullable @SuppressLint("NullableCollection") + List<QzssSatelliteEphemeris> satelliteEphemeris) { + mSatelliteEphemeris = satelliteEphemeris; + return this; + } + + /** Sets the list of real time integrity model. */ + @NonNull + public Builder setRealTimeIntegrityModels( + @Nullable @SuppressLint("NullableCollection") + List<RealTimeIntegrityModel> realTimeIntegrityModels) { + mRealTimeIntegrityModels = realTimeIntegrityModels; + return this; + } + + /** Sets the list of QZSS satellite correction. */ + @NonNull + public Builder setSatelliteCorrections( + @Nullable @SuppressLint("NullableCollection") + List<GnssSatelliteCorrections> satelliteCorrections) { + mSatelliteCorrections = satelliteCorrections; + return this; + } + + /** Builds a {@link QzssAssistance} instance as specified by this builder. */ + @NonNull + public QzssAssistance build() { + return new QzssAssistance(this); + } + } +} diff --git a/location/java/android/location/QzssSatelliteEphemeris.java b/location/java/android/location/QzssSatelliteEphemeris.java new file mode 100644 index 000000000000..96203d9588c8 --- /dev/null +++ b/location/java/android/location/QzssSatelliteEphemeris.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.GpsSatelliteEphemeris.GpsL2Params; +import android.location.GpsSatelliteEphemeris.GpsSatelliteClockModel; +import android.location.GpsSatelliteEphemeris.GpsSatelliteHealth; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains ephemeris parameters specific to QZSS satellites. + * + * <p>This is defined in IS-QZSS-PNT section 4.1.2. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class QzssSatelliteEphemeris implements Parcelable { + /** Satellite PRN. */ + private final int mPrn; + + /** L2 parameters. */ + @NonNull private final GpsL2Params mGpsL2Params; + + /** Clock model. */ + @NonNull private final GpsSatelliteClockModel mSatelliteClockModel; + + /** Orbit model. */ + @NonNull private final KeplerianOrbitModel mSatelliteOrbitModel; + + /** Satellite health. */ + @NonNull private final GpsSatelliteHealth mSatelliteHealth; + + /** Ephemeris time. */ + @NonNull private final SatelliteEphemerisTime mSatelliteEphemerisTime; + + /** Returns the PRN of the satellite. */ + @IntRange(from = 183, to = 206) + public int getPrn() { + return mPrn; + } + + /** Returns the L2 parameters of the satellite. */ + @NonNull + public GpsL2Params getGpsL2Params() { + return mGpsL2Params; + } + + /** Returns the clock model of the satellite. */ + @NonNull + public GpsSatelliteClockModel getSatelliteClockModel() { + return mSatelliteClockModel; + } + + /** Returns the orbit model of the satellite. */ + @NonNull + public KeplerianOrbitModel getSatelliteOrbitModel() { + return mSatelliteOrbitModel; + } + + /** Returns the satellite health. */ + @NonNull + public GpsSatelliteHealth getSatelliteHealth() { + return mSatelliteHealth; + } + + /** Returns the ephemeris time. */ + @NonNull + public SatelliteEphemerisTime getSatelliteEphemerisTime() { + return mSatelliteEphemerisTime; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mPrn); + parcel.writeTypedObject(mGpsL2Params, flags); + parcel.writeTypedObject(mSatelliteClockModel, flags); + parcel.writeTypedObject(mSatelliteOrbitModel, flags); + parcel.writeTypedObject(mSatelliteHealth, flags); + parcel.writeTypedObject(mSatelliteEphemerisTime, flags); + } + + private QzssSatelliteEphemeris(Builder builder) { + // Allow PRN beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mPrn >= 1); + Preconditions.checkNotNull(builder.mGpsL2Params, "GpsL2Params cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteClockModel, + "SatelliteClockModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteOrbitModel, + "SatelliteOrbitModel cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteHealth, + "SatelliteHealth cannot be null"); + Preconditions.checkNotNull(builder.mSatelliteEphemerisTime, + "SatelliteEphemerisTime cannot be null"); + mPrn = builder.mPrn; + mGpsL2Params = builder.mGpsL2Params; + mSatelliteClockModel = builder.mSatelliteClockModel; + mSatelliteOrbitModel = builder.mSatelliteOrbitModel; + mSatelliteHealth = builder.mSatelliteHealth; + mSatelliteEphemerisTime = builder.mSatelliteEphemerisTime; + } + + public static final @NonNull Creator<QzssSatelliteEphemeris> CREATOR = + new Creator<QzssSatelliteEphemeris>() { + @Override + @NonNull + public QzssSatelliteEphemeris createFromParcel(Parcel in) { + final QzssSatelliteEphemeris.Builder qzssSatelliteEphemeris = + new Builder() + .setPrn(in.readInt()) + .setGpsL2Params(in.readTypedObject(GpsL2Params.CREATOR)) + .setSatelliteClockModel( + in.readTypedObject(GpsSatelliteClockModel.CREATOR)) + .setSatelliteOrbitModel( + in.readTypedObject(KeplerianOrbitModel.CREATOR)) + .setSatelliteHealth( + in.readTypedObject(GpsSatelliteHealth.CREATOR)) + .setSatelliteEphemerisTime( + in.readTypedObject(SatelliteEphemerisTime.CREATOR)); + return qzssSatelliteEphemeris.build(); + } + + @Override + public QzssSatelliteEphemeris[] newArray(int size) { + return new QzssSatelliteEphemeris[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("QzssSatelliteEphemeris["); + builder.append("prn=").append(mPrn); + builder.append(", gpsL2Params=").append(mGpsL2Params); + builder.append(", satelliteClockModel=").append(mSatelliteClockModel); + builder.append(", satelliteOrbitModel=").append(mSatelliteOrbitModel); + builder.append(", satelliteHealth=").append(mSatelliteHealth); + builder.append(", satelliteEphemerisTime=").append(mSatelliteEphemerisTime); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link QzssSatelliteEphemeris}. */ + public static final class Builder { + private int mPrn; + private GpsL2Params mGpsL2Params; + private GpsSatelliteClockModel mSatelliteClockModel; + private KeplerianOrbitModel mSatelliteOrbitModel; + private GpsSatelliteHealth mSatelliteHealth; + private SatelliteEphemerisTime mSatelliteEphemerisTime; + + /** Sets the PRN of the satellite. */ + @NonNull + public Builder setPrn(@IntRange(from = 183, to = 206) int prn) { + mPrn = prn; + return this; + } + + /** Sets the L2 parameters of the satellite. */ + @NonNull + public Builder setGpsL2Params(@NonNull GpsL2Params gpsL2Params) { + mGpsL2Params = gpsL2Params; + return this; + } + + /** Sets the clock model of the satellite. */ + @NonNull + public Builder setSatelliteClockModel(@NonNull GpsSatelliteClockModel satelliteClockModel) { + mSatelliteClockModel = satelliteClockModel; + return this; + } + + /** Sets the orbit model of the satellite. */ + @NonNull + public Builder setSatelliteOrbitModel(@NonNull KeplerianOrbitModel satelliteOrbitModel) { + mSatelliteOrbitModel = satelliteOrbitModel; + return this; + } + + /** Sets the satellite health. */ + @NonNull + public Builder setSatelliteHealth(@NonNull GpsSatelliteHealth satelliteHealth) { + mSatelliteHealth = satelliteHealth; + return this; + } + + /** Sets the ephemeris time. */ + @NonNull + public Builder setSatelliteEphemerisTime( + @NonNull SatelliteEphemerisTime satelliteEphemerisTime) { + mSatelliteEphemerisTime = satelliteEphemerisTime; + return this; + } + + /** Builds a {@link QzssSatelliteEphemeris} instance as specified by this builder. */ + @NonNull + public QzssSatelliteEphemeris build() { + return new QzssSatelliteEphemeris(this); + } + } +} diff --git a/location/java/android/location/RealTimeIntegrityModel.java b/location/java/android/location/RealTimeIntegrityModel.java new file mode 100644 index 000000000000..d268926e56e2 --- /dev/null +++ b/location/java/android/location/RealTimeIntegrityModel.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains the real time integrity status of a GNSS satellite based on notice advisory. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class RealTimeIntegrityModel implements Parcelable { + /** + * Pseudo-random or satellite ID number for the satellite, + * a.k.a. Space Vehicle (SV), or OSN number for Glonass. + * + * <p>The distinction is made by looking at the constellation field. Values + * must be in the range of: + * + * <p> - GPS: 1-32 + * <p> - GLONASS: 1-25 + * <p> - QZSS: 183-206 + * <p> - Galileo: 1-36 + * <p> - Beidou: 1-63 + */ + private final int mSvid; + + /** Indicates whether the satellite is currently usable for navigation. */ + private final boolean mUsable; + + /** UTC timestamp (in seconds) when the advisory was published. */ + private final long mPublishDateSeconds; + + /** UTC timestamp (in seconds) for the start of the event. */ + private final long mStartDateSeconds; + + /** UTC timestamp (in seconds) for the end of the event. */ + private final long mEndDateSeconds; + + /** + * Abbreviated type of the advisory, providing a concise summary of the event. + * + * <p>This field follows different definitions depending on the GNSS constellation: + * <p> - GPS: See NANU type definitions(https://www.navcen.uscg.gov/nanu-abbreviations-and-descriptions) + * <p> - Galileo: See NAGU type definitions(https://www.gsc-europa.eu/system-service-status/nagu-information) + * <p> - QZSS: See NAQU type definitions](https://sys.qzss.go.jp/dod/en/naqu/type.html) + * <p> - BeiDou: Not used; set to an empty string. + */ + @NonNull private final String mAdvisoryType; + + /** + * Unique identifier for the advisory within its constellation's system. + * + * <p>For BeiDou, this is not used and should be an empty string. + */ + @NonNull private final String mAdvisoryNumber; + + private RealTimeIntegrityModel(Builder builder) { + // Allow SV ID beyond the range to support potential future extensibility. + Preconditions.checkArgument(builder.mSvid >= 1); + Preconditions.checkArgument(builder.mPublishDateSeconds > 0); + Preconditions.checkArgument(builder.mStartDateSeconds > 0); + Preconditions.checkArgument(builder.mEndDateSeconds > 0); + Preconditions.checkNotNull(builder.mAdvisoryType, "AdvisoryType cannot be null"); + Preconditions.checkNotNull(builder.mAdvisoryNumber, "AdvisoryNumber cannot be null"); + mSvid = builder.mSvid; + mUsable = builder.mUsable; + mPublishDateSeconds = builder.mPublishDateSeconds; + mStartDateSeconds = builder.mStartDateSeconds; + mEndDateSeconds = builder.mEndDateSeconds; + mAdvisoryType = builder.mAdvisoryType; + mAdvisoryNumber = builder.mAdvisoryNumber; + } + + /** + * Returns the Pseudo-random or satellite ID number for the satellite, + * a.k.a. Space Vehicle (SV), or OSN number for Glonass. + * + * <p>The distinction is made by looking at the constellation field. Values + * must be in the range of: + * + * <p> - GPS: 1-32 + * <p> - GLONASS: 1-25 + * <p> - QZSS: 183-206 + * <p> - Galileo: 1-36 + * <p> - Beidou: 1-63 + */ + @IntRange(from = 1, to = 206) + public int getSvid() { + return mSvid; + } + + /** Returns whether the satellite is usable or not. */ + public boolean isUsable() { + return mUsable; + } + + /** Returns the UTC timestamp (in seconds) when the advisory was published */ + @IntRange(from = 0) + public long getPublishDateSeconds() { + return mPublishDateSeconds; + } + + /** Returns UTC timestamp (in seconds) for the start of the event. */ + @IntRange(from = 0) + public long getStartDateSeconds() { + return mStartDateSeconds; + } + + /** Returns UTC timestamp (in seconds) for the end of the event. */ + @IntRange(from = 0) + public long getEndDateSeconds() { + return mEndDateSeconds; + } + + /** Returns the abbreviated type of notice advisory. */ + @NonNull + public String getAdvisoryType() { + return mAdvisoryType; + } + + /** Returns the unique identifier for the advisory. */ + @NonNull + public String getAdvisoryNumber() { + return mAdvisoryNumber; + } + + public static final @NonNull Creator<RealTimeIntegrityModel> CREATOR = + new Creator<RealTimeIntegrityModel>() { + @Override + @NonNull + public RealTimeIntegrityModel createFromParcel(Parcel in) { + RealTimeIntegrityModel realTimeIntegrityModel = + new RealTimeIntegrityModel.Builder() + .setSvid(in.readInt()) + .setUsable(in.readBoolean()) + .setPublishDateSeconds(in.readLong()) + .setStartDateSeconds(in.readLong()) + .setEndDateSeconds(in.readLong()) + .setAdvisoryType(in.readString8()) + .setAdvisoryNumber(in.readString8()) + .build(); + return realTimeIntegrityModel; + } + + @Override + public RealTimeIntegrityModel[] newArray(int size) { + return new RealTimeIntegrityModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mSvid); + parcel.writeBoolean(mUsable); + parcel.writeLong(mPublishDateSeconds); + parcel.writeLong(mStartDateSeconds); + parcel.writeLong(mEndDateSeconds); + parcel.writeString8(mAdvisoryType); + parcel.writeString8(mAdvisoryNumber); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("RealTimeIntegrityModel["); + builder.append("svid = ").append(mSvid); + builder.append(", usable = ").append(mUsable); + builder.append(", publishDateSeconds = ").append(mPublishDateSeconds); + builder.append(", startDateSeconds = ").append(mStartDateSeconds); + builder.append(", endDateSeconds = ").append(mEndDateSeconds); + builder.append(", advisoryType = ").append(mAdvisoryType); + builder.append(", advisoryNumber = ").append(mAdvisoryNumber); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link RealTimeIntegrityModel} */ + public static final class Builder { + private int mSvid; + private boolean mUsable; + private long mPublishDateSeconds; + private long mStartDateSeconds; + private long mEndDateSeconds; + private String mAdvisoryType; + private String mAdvisoryNumber; + + /** + * Sets the Pseudo-random or satellite ID number for the satellite, + * a.k.a. Space Vehicle (SV), or OSN number for Glonass. + * + * <p>The distinction is made by looking at the constellation field. Values + * must be in the range of: + * + * <p> - GPS: 1-32 + * <p> - GLONASS: 1-25 + * <p> - QZSS: 183-206 + * <p> - Galileo: 1-36 + * <p> - Beidou: 1-63 + */ + @NonNull + public Builder setSvid(@IntRange(from = 1, to = 206) int svid) { + mSvid = svid; + return this; + } + + /** Sets whether the satellite is usable or not. */ + @NonNull + public Builder setUsable(boolean usable) { + mUsable = usable; + return this; + } + + /** Sets the UTC timestamp (in seconds) when the advisory was published. */ + @NonNull + public Builder setPublishDateSeconds(@IntRange(from = 0) long publishDateSeconds) { + mPublishDateSeconds = publishDateSeconds; + return this; + } + + /** Sets the UTC timestamp (in seconds) for the start of the event. */ + @NonNull + public Builder setStartDateSeconds(@IntRange(from = 0) long startDateSeconds) { + mStartDateSeconds = startDateSeconds; + return this; + } + + /** Sets the UTC timestamp (in seconds) for the end of the event. */ + @NonNull + public Builder setEndDateSeconds(@IntRange(from = 0) long endDateSeconds) { + mEndDateSeconds = endDateSeconds; + return this; + } + + /** Sets the abbreviated type of notice advisory. */ + @NonNull + public Builder setAdvisoryType(@NonNull String advisoryType) { + mAdvisoryType = advisoryType; + return this; + } + + /** Sets the unique identifier for the advisory. */ + @NonNull + public Builder setAdvisoryNumber(@NonNull String advisoryNumber) { + mAdvisoryNumber = advisoryNumber; + return this; + } + + /** Builds a {@link RealTimeIntegrityModel} instance as specified by this builder. */ + @NonNull + public RealTimeIntegrityModel build() { + return new RealTimeIntegrityModel(this); + } + } +} diff --git a/location/java/android/location/SatelliteEphemerisTime.java b/location/java/android/location/SatelliteEphemerisTime.java new file mode 100644 index 000000000000..0ab3acfe3147 --- /dev/null +++ b/location/java/android/location/SatelliteEphemerisTime.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains time of ephemeris for GPS, Galileo, and QZSS. + * + * <p>For GPS, this is defined in IS-GPS-200, section 20.3.3.4.1. + * <p>For Galileo, this is defined in Galileo-OS-SIS-ICD, section 5.1.2, 5.1.9.2. + * <p>For QZSS, this is defined in IS-QZSS-200, section 4.1.2.4. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class SatelliteEphemerisTime implements Parcelable { + /** The issue of ephemeris data. */ + private final int mIode; + + /** The satellite week number without rollover. */ + private final int mWeekNumber; + + /** The broadcast time of ephemeris in GNSS time of week in seconds. */ + private final int mToeSeconds; + + private SatelliteEphemerisTime(Builder builder) { + Preconditions.checkArgumentInRange(builder.mIode, 0, 1023, "Iode"); + Preconditions.checkArgument(builder.mWeekNumber >= 0); + Preconditions.checkArgumentInRange(builder.mToeSeconds, 0, 604799, "ToeSeconds"); + mIode = builder.mIode; + mWeekNumber = builder.mWeekNumber; + mToeSeconds = builder.mToeSeconds; + } + + /** Returns the issue of ephemeris data. */ + @IntRange(from = 0, to = 1023) + public int getIode() { + return mIode; + } + + /** Returns the satellite week number without rollover. */ + @IntRange(from = 0) + public int getWeekNumber() { + return mWeekNumber; + } + + /** Returns the broadcast time of ephemeris in GNSS time of week in seconds. */ + @IntRange(from = 0, to = 604799) + public int getToeSeconds() { + return mToeSeconds; + } + + public static final @NonNull Creator<SatelliteEphemerisTime> CREATOR = + new Creator<SatelliteEphemerisTime>() { + @Override + public SatelliteEphemerisTime createFromParcel(Parcel in) { + final SatelliteEphemerisTime.Builder satelliteEphemerisTime = + new Builder() + .setIode(in.readInt()) + .setWeekNumber(in.readInt()) + .setToeSeconds(in.readInt()); + return satelliteEphemerisTime.build(); + } + + @Override + public SatelliteEphemerisTime[] newArray(int size) { + return new SatelliteEphemerisTime[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mIode); + parcel.writeInt(mWeekNumber); + parcel.writeInt(mToeSeconds); + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("SatelliteEphemerisTime["); + builder.append("iode = ").append(mIode); + builder.append(", weekNumber = ").append(mWeekNumber); + builder.append(", toeSeconds = ").append(mToeSeconds); + builder.append("]"); + return builder.toString(); + } + + /** Builder for {@link SatelliteEphemerisTime}. */ + public static final class Builder { + private int mIode; + private int mWeekNumber; + private int mToeSeconds; + + /** Sets the issue of ephemeris data. */ + @NonNull + public Builder setIode(@IntRange(from = 0, to = 1023) int iode) { + mIode = iode; + return this; + } + + /** Sets the satellite week number without rollover. */ + @NonNull + public Builder setWeekNumber(@IntRange(from = 0) int weekNumber) { + mWeekNumber = weekNumber; + return this; + } + + /** Sets the broadcast time of ephemeris in GNSS time of week in seconds. */ + @NonNull + public Builder setToeSeconds(@IntRange(from = 0, to = 604799) int toeSeconds) { + mToeSeconds = toeSeconds; + return this; + } + + /** Builds a {@link SatelliteEphemerisTime} instance as specified by this builder. */ + @NonNull + public SatelliteEphemerisTime build() { + return new SatelliteEphemerisTime(this); + } + } +} diff --git a/location/java/android/location/TimeModel.java b/location/java/android/location/TimeModel.java new file mode 100644 index 000000000000..380f7b87d8f6 --- /dev/null +++ b/location/java/android/location/TimeModel.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.GnssStatus.ConstellationType; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains the GNSS-GNSS system time offset between the GNSS system time. + * + * <p>This is defined in IS-GPS-200 section 30.3.3.8.2. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class TimeModel implements Parcelable { + /* + * Model represents parameters to convert from current GNSS to GNSS system + * time indicated by toGnss. + */ + private final @ConstellationType int mToGnss; + + /** Bias coefficient of GNSS time scale relative to GNSS time scale in seconds. */ + private final double mA0; + + /** Drift coefficient of GNSS time scale relative to GNSS time scale in seconds per second. */ + private final double mA1; + + /** GNSS time of week in seconds. */ + private final int mTimeOfWeek; + + /** Week number of the GNSS time. */ + private final int mWeekNumber; + + private TimeModel(Builder builder) { + Preconditions.checkArgumentInRange( + builder.mToGnss, + GnssStatus.CONSTELLATION_UNKNOWN, + GnssStatus.CONSTELLATION_COUNT, + "ToGnss"); + Preconditions.checkArgumentInRange(builder.mA0, -1.0f, 1.0f, "A0"); + Preconditions.checkArgumentInRange(builder.mA1, -3.28e-6f, 3.28e-6f, "A1"); + Preconditions.checkArgumentInRange(builder.mTimeOfWeek, 0, 604800, "TimeOfWeek"); + Preconditions.checkArgument(builder.mWeekNumber >= 0); + mToGnss = builder.mToGnss; + mA0 = builder.mA0; + mA1 = builder.mA1; + mTimeOfWeek = builder.mTimeOfWeek; + mWeekNumber = builder.mWeekNumber; + } + + /** Returns the constellation type to convert from current GNSS system time. */ + @ConstellationType + public int getToGnss() { + return mToGnss; + } + + /** Returns the bias coefficient of GNSS time scale relative to GNSS time scale in seconds. */ + @FloatRange(from = -1.0f, to = 1.0f) + public double getA0() { + return mA0; + } + + /** + * Returns the drift coefficient of GNSS time scale relative to GNSS time scale in seconds per + * second. + */ + @FloatRange(from = -3.28e-6f, to = 3.28e-6f) + public double getA1() { + return mA1; + } + + /** Returns the GNSS time of week in seconds. */ + @IntRange(from = 0, to = 604800) + public int getTimeOfWeek() { + return mTimeOfWeek; + } + + /** Returns the week number of the GNSS time. */ + @IntRange(from = 0) + public int getWeekNumber() { + return mWeekNumber; + } + + public static final @NonNull Creator<TimeModel> CREATOR = + new Creator<TimeModel>() { + @Override + public TimeModel createFromParcel(@NonNull Parcel source) { + return new TimeModel.Builder() + .setToGnss(source.readInt()) + .setA0(source.readDouble()) + .setA1(source.readDouble()) + .setTimeOfWeek(source.readInt()) + .setWeekNumber(source.readInt()) + .build(); + } + + @Override + public TimeModel[] newArray(int size) { + return new TimeModel[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("TimeModel["); + builder.append("toGnss = ").append(mToGnss); + builder.append(", a0 = ").append(mA0); + builder.append(", a1 = ").append(mA1); + builder.append(", timeOfWeek = ").append(mTimeOfWeek); + builder.append(", weekNumber = ").append(mWeekNumber); + builder.append("]"); + return builder.toString(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mToGnss); + dest.writeDouble(mA0); + dest.writeDouble(mA1); + dest.writeInt(mTimeOfWeek); + dest.writeInt(mWeekNumber); + } + + /** Builder for {@link TimeModel} */ + public static final class Builder { + + private @ConstellationType int mToGnss; + private double mA0; + private double mA1; + private int mTimeOfWeek; + private int mWeekNumber; + + /** Sets the constellation type to convert from current GNSS system time. */ + @NonNull + public Builder setToGnss(@ConstellationType int toGnss) { + mToGnss = toGnss; + return this; + } + + /** Sets the bias coefficient of GNSS time scale relative to GNSS time scale in seconds. */ + @NonNull + public Builder setA0(@FloatRange(from = -1.0f, to = 1.0f) double a0) { + mA0 = a0; + return this; + } + + /** + * Sets the drift coefficient of GNSS time scale relative to GNSS time scale in seconds per + * second. + */ + @NonNull + public Builder setA1(@FloatRange(from = -3.28e-6f, to = 3.28e-6f) double a1) { + mA1 = a1; + return this; + } + + /** Sets the GNSS time of week in seconds. */ + @NonNull + public Builder setTimeOfWeek(@IntRange(from = 0, to = 604800) int timeOfWeek) { + mTimeOfWeek = timeOfWeek; + return this; + } + + /** Sets the week number of the GNSS time. */ + @NonNull + public Builder setWeekNumber(@IntRange(from = 0) int weekNumber) { + mWeekNumber = weekNumber; + return this; + } + + /** Builds the {@link TimeModel} object. */ + @NonNull + public TimeModel build() { + return new TimeModel(this); + } + } +} diff --git a/location/java/android/location/UtcModel.java b/location/java/android/location/UtcModel.java new file mode 100644 index 000000000000..6dc633de3fcb --- /dev/null +++ b/location/java/android/location/UtcModel.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.location; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.location.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * A class contains parameters to convert from current GNSS time to UTC time. + * + * <p>This is defined in RINEX 3.05 "TIME SYSTEM CORR" in table A5. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE) +@SystemApi +public final class UtcModel implements Parcelable { + /** Bias coefficient of GNSS time scale relative to UTC time scale in seconds. */ + private final double mA0; + + /** Drift coefficient of GNSS time scale relative to UTC time scale in seconds per second. */ + private final double mA1; + + /** Reference GNSS time of week in seconds. */ + private final int mTimeOfWeek; + + /** Reference GNSS week number. */ + private final int mWeekNumber; + + private UtcModel(Builder builder) { + Preconditions.checkArgumentInRange(builder.mA0, -2.0f, 2.0f, "A0"); + Preconditions.checkArgumentInRange(builder.mA1, -7.45e-9f, 7.45e-9f, "A1"); + Preconditions.checkArgumentInRange(builder.mTimeOfWeek, 0, 604800, "TimeOfWeek"); + Preconditions.checkArgument(builder.mWeekNumber >= 0); + mA0 = builder.mA0; + mA1 = builder.mA1; + mTimeOfWeek = builder.mTimeOfWeek; + mWeekNumber = builder.mWeekNumber; + } + + /** Returns the bias coefficient of GNSS time scale relative to UTC time scale in seconds. */ + @FloatRange(from = -2.0f, to = 2.0f) + public double getA0() { + return mA0; + } + + /** + * Returns the drift coefficient of GNSS time scale relative to UTC time scale in seconds per + * second. + */ + @FloatRange(from = -7.45e-9f, to = 7.45e-9f) + public double getA1() { + return mA1; + } + + /** Returns the reference GNSS time of week in seconds. */ + @IntRange(from = 0, to = 604800) + public int getTimeOfWeek() { + return mTimeOfWeek; + } + + /** Returns the reference GNSS week number. */ + @IntRange(from = 0) + public int getWeekNumber() { + return mWeekNumber; + } + + @Override + public int describeContents() { + return 0; + } + @Override + @NonNull + public String toString() { + StringBuilder builder = new StringBuilder("UtcModel["); + builder.append("a0 = ").append(mA0); + builder.append(", a1 = ").append(mA1); + builder.append(", timeOfWeek = ").append(mTimeOfWeek); + builder.append(", weekNumber = ").append(mWeekNumber); + builder.append("]"); + return builder.toString(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeDouble(mA0); + dest.writeDouble(mA1); + dest.writeInt(mTimeOfWeek); + dest.writeInt(mWeekNumber); + } + + public static final @NonNull Creator<UtcModel> CREATOR = + new Creator<UtcModel>() { + @Override + public UtcModel createFromParcel(@NonNull Parcel source) { + return new UtcModel.Builder() + .setA0(source.readDouble()) + .setA1(source.readDouble()) + .setTimeOfWeek(source.readInt()) + .setWeekNumber(source.readInt()) + .build(); + } + + @Override + public UtcModel[] newArray(int size) { + return new UtcModel[size]; + } + }; + + /** Builder for {@link UtcModel}. */ + public static final class Builder { + private double mA0; + private double mA1; + private int mTimeOfWeek; + private int mWeekNumber; + + /** Sets the bias coefficient of GNSS time scale relative to UTC time scale in seconds. */ + @NonNull + public Builder setA0(@FloatRange(from = -2.0f, to = 2.0f) double a0) { + mA0 = a0; + return this; + } + + /** + * Sets the drift coefficient of GNSS time scale relative to UTC time scale in seconds per + * second. + */ + @NonNull + public Builder setA1(@FloatRange(from = -7.45e-9f, to = 7.45e-9f) double a1) { + mA1 = a1; + return this; + } + + /** Sets the reference GNSS time of week in seconds. */ + @NonNull + public Builder setTimeOfWeek(@IntRange(from = 0, to = 604800) int timeOfWeek) { + mTimeOfWeek = timeOfWeek; + return this; + } + + /** Sets the reference GNSS week number. */ + @NonNull + public Builder setWeekNumber(@IntRange(from = 0) int weekNumber) { + mWeekNumber = weekNumber; + return this; + } + + /** Builds a {@link UtcModel} instance as specified by this builder. */ + @NonNull + public UtcModel build() { + return new UtcModel(this); + } + } +} diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index 5395206155b3..c02cc808d60c 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -161,3 +161,10 @@ flag { description: "Flag for gating the density-based coarse locations" bug: "376198890" } + +flag { + name: "gnss_assistance_interface" + namespace: "location" + description: "Flag for GNSS assistance interface" + bug: "209078566" +}
\ No newline at end of file diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index b41f40f412d2..5689df0784f0 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -548,8 +548,45 @@ public final class AudioDeviceInfo { return counts; } + /** @hide */ + @IntDef(flag = true, prefix = "AudioFormat.CHANNEL_OUT_", value = { + AudioFormat.CHANNEL_INVALID, + AudioFormat.CHANNEL_OUT_DEFAULT, + AudioFormat.CHANNEL_OUT_FRONT_LEFT, + AudioFormat.CHANNEL_OUT_FRONT_RIGHT, + AudioFormat.CHANNEL_OUT_FRONT_CENTER, + AudioFormat.CHANNEL_OUT_LOW_FREQUENCY, + AudioFormat.CHANNEL_OUT_BACK_LEFT, + AudioFormat.CHANNEL_OUT_BACK_RIGHT, + AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER, + AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER, + AudioFormat.CHANNEL_OUT_BACK_CENTER, + AudioFormat.CHANNEL_OUT_SIDE_LEFT, + AudioFormat.CHANNEL_OUT_SIDE_RIGHT, + AudioFormat.CHANNEL_OUT_TOP_CENTER, + AudioFormat.CHANNEL_OUT_TOP_FRONT_LEFT, + AudioFormat.CHANNEL_OUT_TOP_FRONT_CENTER, + AudioFormat.CHANNEL_OUT_TOP_FRONT_RIGHT, + AudioFormat.CHANNEL_OUT_TOP_BACK_LEFT, + AudioFormat.CHANNEL_OUT_TOP_BACK_CENTER, + AudioFormat.CHANNEL_OUT_TOP_BACK_RIGHT, + AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT, + AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT, + AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT, + AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER, + AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT, + AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2, + AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT, + AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT} + ) + @Retention(RetentionPolicy.SOURCE) + @FlaggedApi(FLAG_SPEAKER_LAYOUT_API) + public @interface SpeakerLayoutChannelMask {} + /** - * @return A ChannelMask representing the physical output speaker layout of the device. + * @return A ChannelMask representing the speaker layout of a TYPE_BUILTIN_SPEAKER device. + * + * Valid only for speakers built-in to the device. * * The layout channel mask only indicates which speaker channels are present, the * physical layout of the speakers should be informed by a standard for multi-channel @@ -557,6 +594,7 @@ public final class AudioDeviceInfo { * @see AudioFormat */ @FlaggedApi(FLAG_SPEAKER_LAYOUT_API) + @SpeakerLayoutChannelMask public int getSpeakerLayoutChannelMask() { return mPort.speakerLayoutChannelMask(); } diff --git a/media/java/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/ActiveProcessingPicture.aidl new file mode 100644 index 000000000000..2851306f6e4d --- /dev/null +++ b/media/java/android/media/quality/ActiveProcessingPicture.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.quality; + +parcelable ActiveProcessingPicture;
\ No newline at end of file diff --git a/media/java/android/media/quality/ActiveProcessingPicture.java b/media/java/android/media/quality/ActiveProcessingPicture.java new file mode 100644 index 000000000000..e16ad62e23f2 --- /dev/null +++ b/media/java/android/media/quality/ActiveProcessingPicture.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.quality; + +import android.annotation.FlaggedApi; +import android.media.tv.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +/** + * Active picture represents an image or video undergoing picture processing which uses a picture + * profile. The picture profile is used to configure the picture processing parameters. + */ +@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) +public final class ActiveProcessingPicture implements Parcelable { + private final int mId; + private final String mProfileId; + + public ActiveProcessingPicture(int id, @NonNull String profileId) { + mId = id; + mProfileId = profileId; + } + + /** @hide */ + ActiveProcessingPicture(Parcel in) { + mId = in.readInt(); + mProfileId = in.readString(); + } + + @NonNull + public static final Creator<ActiveProcessingPicture> CREATOR = new Creator<>() { + @Override + public ActiveProcessingPicture createFromParcel(Parcel in) { + return new ActiveProcessingPicture(in); + } + + @Override + public ActiveProcessingPicture[] newArray(int size) { + return new ActiveProcessingPicture[size]; + } + }; + + /** + * An ID that uniquely identifies the active content. + * + * <p>The ID is assigned by the system to distinguish different active contents. + */ + public int getId() { + return mId; + } + + /** + * The ID of the picture profile used to configure the content. + */ + @NonNull + public String getProfileId() { + return mProfileId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString(mProfileId); + } +} diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index 9daebca98e3e..253c2d896d63 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -25,51 +25,56 @@ import android.media.quality.PictureProfileHandle; import android.media.quality.PictureProfile; import android.media.quality.SoundProfileHandle; import android.media.quality.SoundProfile; +import android.os.UserHandle; /** * Interface for Media Quality Manager * @hide */ interface IMediaQualityManager { - PictureProfile createPictureProfile(in PictureProfile pp, int userId); - void updatePictureProfile(in String id, in PictureProfile pp, int userId); - void removePictureProfile(in String id, int userId); - PictureProfile getPictureProfile(in int type, in String name, int userId); - List<PictureProfile> getPictureProfilesByPackage(in String packageName, int userId); - List<PictureProfile> getAvailablePictureProfiles(int userId); - boolean setDefaultPictureProfile(in String id, int userId); - List<String> getPictureProfilePackageNames(int userId); - List<String> getPictureProfileAllowList(int userId); - void setPictureProfileAllowList(in List<String> packages, int userId); - List<PictureProfileHandle> getPictureProfileHandle(in String[] id, int userId); + PictureProfile createPictureProfile(in PictureProfile pp, in UserHandle user); + void updatePictureProfile(in String id, in PictureProfile pp, in UserHandle user); + void removePictureProfile(in String id, in UserHandle user); + boolean setDefaultPictureProfile(in String id, in UserHandle user); + PictureProfile getPictureProfile( + in int type, in String name, in boolean includeParams, in UserHandle user); + List<PictureProfile> getPictureProfilesByPackage( + in String packageName, in boolean includeParams, in UserHandle user); + List<PictureProfile> getAvailablePictureProfiles(in boolean includeParams, in UserHandle user); + List<String> getPictureProfilePackageNames(in UserHandle user); + List<String> getPictureProfileAllowList(in UserHandle user); + void setPictureProfileAllowList(in List<String> packages, in UserHandle user); + List<PictureProfileHandle> getPictureProfileHandle(in String[] id, in UserHandle user); - SoundProfile createSoundProfile(in SoundProfile pp, int userId); - void updateSoundProfile(in String id, in SoundProfile pp, int userId); - void removeSoundProfile(in String id, int userId); - SoundProfile getSoundProfile(in int type, in String name, int userId); - List<SoundProfile> getSoundProfilesByPackage(in String packageName, int userId); - List<SoundProfile> getAvailableSoundProfiles(int userId); - boolean setDefaultSoundProfile(in String id, int userId); - List<String> getSoundProfilePackageNames(int userId); - List<String> getSoundProfileAllowList(int userId); - void setSoundProfileAllowList(in List<String> packages, int userId); - List<SoundProfileHandle> getSoundProfileHandle(in String[] id, int userId); + SoundProfile createSoundProfile(in SoundProfile pp, in UserHandle user); + void updateSoundProfile(in String id, in SoundProfile pp, in UserHandle user); + void removeSoundProfile(in String id, in UserHandle user); + boolean setDefaultSoundProfile(in String id, in UserHandle user); + SoundProfile getSoundProfile( + in int type, in String name, in boolean includeParams, in UserHandle user); + List<SoundProfile> getSoundProfilesByPackage( + in String packageName, in boolean includeParams, in UserHandle user); + List<SoundProfile> getAvailableSoundProfiles(in boolean includeParams, in UserHandle user); + List<String> getSoundProfilePackageNames(in UserHandle user); + List<String> getSoundProfileAllowList(in UserHandle user); + void setSoundProfileAllowList(in List<String> packages, in UserHandle user); + List<SoundProfileHandle> getSoundProfileHandle(in String[] id, in UserHandle user); void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb); - List<ParamCapability> getParamCapabilities(in List<String> names, int userId); + List<ParamCapability> getParamCapabilities(in List<String> names, in UserHandle user); - boolean isSupported(int userId); - void setAutoPictureQualityEnabled(in boolean enabled, int userId); - boolean isAutoPictureQualityEnabled(int userId); - void setSuperResolutionEnabled(in boolean enabled, int userId); - boolean isSuperResolutionEnabled(int userId); - void setAutoSoundQualityEnabled(in boolean enabled, int userId); - boolean isAutoSoundQualityEnabled(int userId); + boolean isSupported(in UserHandle user); + void setAutoPictureQualityEnabled(in boolean enabled, in UserHandle user); + boolean isAutoPictureQualityEnabled(in UserHandle user); + void setSuperResolutionEnabled(in boolean enabled, in UserHandle user); + boolean isSuperResolutionEnabled(in UserHandle user); + void setAutoSoundQualityEnabled(in boolean enabled, in UserHandle user); + boolean isAutoSoundQualityEnabled(in UserHandle user); - void setAmbientBacklightSettings(in AmbientBacklightSettings settings, int userId); - void setAmbientBacklightEnabled(in boolean enabled, int userId); - boolean isAmbientBacklightEnabled(int userId); + void setAmbientBacklightSettings(in AmbientBacklightSettings settings, in UserHandle user); + void setAmbientBacklightEnabled(in boolean enabled, in UserHandle user); + boolean isAmbientBacklightEnabled(in UserHandle user); } diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl index 34aa2b061caf..7071a1684fa2 100644 --- a/media/java/android/media/quality/IPictureProfileCallback.aidl +++ b/media/java/android/media/quality/IPictureProfileCallback.aidl @@ -29,5 +29,5 @@ oneway interface IPictureProfileCallback { void onPictureProfileUpdated(in String id, in PictureProfile p); void onPictureProfileRemoved(in String id, in PictureProfile p); void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps); - void onError(in int err); + void onError(in String id, in int err); } diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl index 9043757316bc..30bb106ef34c 100644 --- a/media/java/android/media/quality/ISoundProfileCallback.aidl +++ b/media/java/android/media/quality/ISoundProfileCallback.aidl @@ -29,5 +29,5 @@ oneway interface ISoundProfileCallback { void onSoundProfileUpdated(in String id, in SoundProfile p); void onSoundProfileRemoved(in String id, in SoundProfile p); void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps); - void onError(in int err); + void onError(in String id, in int err); } diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java index 7b0bd04f3559..6a52bcba547a 100644 --- a/media/java/android/media/quality/MediaQualityContract.java +++ b/media/java/android/media/quality/MediaQualityContract.java @@ -75,11 +75,9 @@ public class MediaQualityContract { public static final String PARAMETER_SATURATION = "saturation"; /** - * @hide - */ - public static final String PARAMETER_COLOR = "color"; - /** - * @hide + * The hue. + * + * <p>Type: INTEGER */ public static final String PARAMETER_HUE = "hue"; @@ -89,47 +87,77 @@ public class MediaQualityContract { public static final String PARAMETER_BACKLIGHT = "backlight"; /** - * @hide + * Adjust brightness in advance color engine. Similar to a "brightness" control on a TV + * but acts at a lower level. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_BRIGHTNESS = "color_tuner_brightness"; /** - * @hide + * Adjust saturation in advance color engine. Similar to a "saturation" control on a TV + * but acts at a lower level. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_SATURATION = "color_tuner_saturation"; /** - * @hide + * Adjust hue in advance color engine. Similar to a "hue" control on a TV but acts at a + * lower level. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_HUE = "color_tuner_hue"; /** - * @hide + * Advance setting for red offset. Adjust the black level of red color channels, it + * controls the minimum intensity of each color, affecting the shadows and + * dark areas of the image. + * + * <p>Type: INTEGER */ - public static final String PARAMETER_COLOR_TUNER_REDO_FFSET = "color_tuner_red_offset"; + public static final String PARAMETER_COLOR_TUNER_RED_OFFSET = "color_tuner_red_offset"; /** - * @hide + * Advance setting for green offset. Adjust the black level of green color channels, it + * controls the minimum intensity of each color, affecting the shadows and dark + * areas of the image. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_GREEN_OFFSET = "color_tuner_green_offset"; /** - * @hide + * Advance setting for blue offset. Adjust the black level of blue color channels, it + * controls the minimum intensity of each color, affecting the shadows and dark areas + * of the image. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_BLUE_OFFSET = "color_tuner_blue_offset"; /** - * @hide + * Advance setting for red gain. Adjust the gain or amplification of the red color channels. + * They control the overall intensity and white balance of red. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_RED_GAIN = "color_tuner_red_gain"; /** - * @hide + * Advance setting for green gain. Adjust the gain or amplification of the green color + * channels. They control the overall intensity and white balance of green. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_GREEN_GAIN = "color_tuner_green_gain"; /** - * @hide + * Advance setting for blue gain. Adjust the gain or amplification of the blue color + * channels.They control the overall intensity and white balance of blue. + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TUNER_BLUE_GAIN = "color_tuner_blue_gain"; @@ -143,33 +171,54 @@ public class MediaQualityContract { */ public static final String PARAMETER_AI_SUPER_RESOLUTION = "ai_super_resolution"; - /** - * @hide + /** Noise reduction. + * (Off, Low, Medium, High) + * @see android.hardware.tv.mediaquality.QualityLevel + * + * <p>Type: STRING */ public static final String PARAMETER_NOISE_REDUCTION = "noise_reduction"; /** - * @hide - */ + * MPEG (moving picture experts group) noise reduction + * (Off, Low, Medium, High) + * @see android.hardware.tv.mediaquality.QualityLevel + * + * <p>Type: STRING + * */ public static final String PARAMETER_MPEG_NOISE_REDUCTION = "mpeg_noise_reduction"; /** - * @hide + * Refine the flesh colors in the pictures without affecting the other colors on the screen. + * (Off, Low, Medium, High) + * @see android.hardware.tv.mediaquality.QualityLevel + * + * <p>Type: STRING */ public static final String PARAMETER_FLESH_TONE = "flesh_tone"; /** - * @hide + * Contour noise reduction. + * (Off, Low, Medium, High) + * @see android.hardware.tv.mediaquality.QualityLevel + * + * <p>Type: STRING */ public static final String PARAMETER_DECONTOUR = "decontour"; /** - * @hide + * Dynamically change picture luma to enhance contrast. + * (Off, Low, Medium, High) + * @see android.hardware.tv.mediaquality.QualityLevel + * + * <p>Type: STRING */ public static final String PARAMETER_DYNAMIC_LUMA_CONTROL = "dynamic_luma_control"; /** - * @hide + * Enable/disable film mode + * + * <p>Type: BOOLEAN */ public static final String PARAMETER_FILM_MODE = "film_mode"; @@ -179,25 +228,50 @@ public class MediaQualityContract { public static final String PARAMETER_BLACK_STRETCH = "black_stretch"; /** - * @hide + * Enable/disable blue color auto stretch + * + * <p>Type: BOOLEAN */ public static final String PARAMETER_BLUE_STRETCH = "blue_stretch"; /** - * @hide + * Enable/disable the overall color tuning feature. + * + * <p>Type: BOOLEAN */ public static final String PARAMETER_COLOR_TUNE = "color_tune"; /** - * @hide + * Adjust color temperature type + * + * <p>Type: INTEGER */ public static final String PARAMETER_COLOR_TEMPERATURE = "color_temperature"; /** - * @hide + * Enable/disable globe dimming. + * + * <p>Type: BOOLEAN */ public static final String PARAMETER_GLOBAL_DIMMING = "global_dimming"; + /** + * Enable/disable auto adjust picture parameter based on the TV content. + * + * <p>Type: BOOLEAN + */ + public static final String PARAMETER_AUTO_PICTURE_QUALITY_ENABLED = + "auto_picture_quality_enabled"; + + /** + * Enable/disable auto upscaling the picture quality. It analyzes the lower-resolution + * image and uses its knowledge to invent the missing pixel, make the image look sharper. + * + * <p>Type: BOOLEAN + */ + public static final String PARAMETER_AUTO_SUPER_RESOLUTION_ENABLED = + "auto_super_resolution_enabled"; + private PictureQuality() { } } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index efbe47ba2f22..7e87462b64de 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -20,11 +20,13 @@ import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.media.tv.flags.Flags; import android.os.RemoteException; +import android.os.UserHandle; import androidx.annotation.RequiresPermission; @@ -47,7 +49,7 @@ public final class MediaQualityManager { private final IMediaQualityManager mService; private final Context mContext; - private final int mUserId; + private final UserHandle mUserHandle; private final Object mLock = new Object(); // @GuardedBy("mLock") private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>(); @@ -55,6 +57,9 @@ public final class MediaQualityManager { private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>(); // @GuardedBy("mLock") private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>(); + // @GuardedBy("mLock") + private final List<ActiveProcessingPictureListenerRecord> mApListenerRecords = + new ArrayList<>(); /** @@ -62,7 +67,7 @@ public final class MediaQualityManager { */ public MediaQualityManager(Context context, IMediaQualityManager service) { mContext = context; - mUserId = context.getUserId(); + mUserHandle = context.getUser(); mService = service; IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() { @Override @@ -102,11 +107,11 @@ public final class MediaQualityManager { } } @Override - public void onError(int err) { + public void onError(String profileId, int err) { synchronized (mLock) { for (PictureProfileCallbackRecord record : mPpCallbackRecords) { // TODO: filter callback record - record.postError(err); + record.postError(profileId, err); } } } @@ -149,11 +154,11 @@ public final class MediaQualityManager { } } @Override - public void onError(int err) { + public void onError(String profileId, int err) { synchronized (mLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { // TODO: filter callback record - record.postError(err); + record.postError(profileId, err); } } } @@ -210,18 +215,21 @@ public final class MediaQualityManager { } } - /** * Gets picture profile by given profile type and name. * + * @param type the type of the profile. + * @param name the name of the profile. + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. * @return the corresponding picture profile if available; {@code null} if the name doesn't - * exist. + * exist. */ @Nullable public PictureProfile getPictureProfile( - @PictureProfile.ProfileType int type, @NonNull String name) { + @PictureProfile.ProfileType int type, @NonNull String name, boolean includeParams) { try { - return mService.getPictureProfile(type, name, mUserId); + return mService.getPictureProfile(type, name, includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -231,14 +239,18 @@ public final class MediaQualityManager { /** * Gets profiles that available to the given package. * + * @param packageName the package name of the profiles. + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. * @hide */ @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) - public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) { + public List<PictureProfile> getPictureProfilesByPackage( + @NonNull String packageName, boolean includeParams) { try { - return mService.getPictureProfilesByPackage(packageName, mUserId); + return mService.getPictureProfilesByPackage(packageName, includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -246,11 +258,16 @@ public final class MediaQualityManager { /** * Gets profiles that available to the caller. + * + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. + * @return the corresponding picture profile if available; {@code null} if the name doesn't + * exist. */ @NonNull - public List<PictureProfile> getAvailablePictureProfiles() { + public List<PictureProfile> getAvailablePictureProfiles(boolean includeParams) { try { - return mService.getAvailablePictureProfiles(mUserId); + return mService.getAvailablePictureProfiles(includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -268,7 +285,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public boolean setDefaultPictureProfile(@Nullable String id) { try { - return mService.setDefaultPictureProfile(id, mUserId); + return mService.setDefaultPictureProfile(id, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -277,7 +294,7 @@ public final class MediaQualityManager { /** * Gets all package names whose picture profiles are available. * - * @see #getPictureProfilesByPackage(String) + * @see #getPictureProfilesByPackage(String, boolean) * @hide */ @SystemApi @@ -285,7 +302,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public List<String> getPictureProfilePackageNames() { try { - return mService.getPictureProfilePackageNames(mUserId); + return mService.getPictureProfilePackageNames(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -297,7 +314,7 @@ public final class MediaQualityManager { */ public List<PictureProfileHandle> getPictureProfileHandle(String[] id) { try { - return mService.getPictureProfileHandle(id, mUserId); + return mService.getPictureProfileHandle(id, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -309,7 +326,7 @@ public final class MediaQualityManager { */ public List<SoundProfileHandle> getSoundProfileHandle(String[] id) { try { - return mService.getSoundProfileHandle(id, mUserId); + return mService.getSoundProfileHandle(id, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -320,10 +337,12 @@ public final class MediaQualityManager { * * <p>If the profile is created successfully, * {@link PictureProfileCallback#onPictureProfileAdded(String, PictureProfile)} is invoked. + * + * @param pp the {@link PictureProfile} object to be created. */ public void createPictureProfile(@NonNull PictureProfile pp) { try { - mService.createPictureProfile(pp, mUserId); + mService.createPictureProfile(pp, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -332,10 +351,13 @@ public final class MediaQualityManager { /** * Updates an existing picture profile and store it in the system. + * + * @param profileId the id of the object to be updated. + * @param pp the {@link PictureProfile} object to be updated. */ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) { try { - mService.updatePictureProfile(profileId, pp, mUserId); + mService.updatePictureProfile(profileId, pp, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -344,10 +366,12 @@ public final class MediaQualityManager { /** * Removes a picture profile from the system. + * + * @param profileId the id of the object to be removed. */ public void removePictureProfile(@NonNull String profileId) { try { - mService.removePictureProfile(profileId, mUserId); + mService.removePictureProfile(profileId, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -383,18 +407,20 @@ public final class MediaQualityManager { } } - /** * Gets sound profile by given profile type and name. * - * @return the corresponding sound profile if available; {@code null} if the name doesn't - * exist. + * @param type the type of the profile. + * @param name the name of the profile. + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. + * @return the corresponding sound profile if available; {@code null} if the name doesn't exist. */ @Nullable public SoundProfile getSoundProfile( - @SoundProfile.ProfileType int type, @NonNull String name) { + @SoundProfile.ProfileType int type, @NonNull String name, boolean includeParams) { try { - return mService.getSoundProfile(type, name, mUserId); + return mService.getSoundProfile(type, name, includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -404,14 +430,18 @@ public final class MediaQualityManager { /** * Gets profiles that available to the given package. * + * @param packageName the package name of the profiles. + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. * @hide */ @SystemApi @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) - public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) { + public List<SoundProfile> getSoundProfilesByPackage( + @NonNull String packageName, boolean includeParams) { try { - return mService.getSoundProfilesByPackage(packageName, mUserId); + return mService.getSoundProfilesByPackage(packageName, includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -419,11 +449,16 @@ public final class MediaQualityManager { /** * Gets profiles that available to the caller package. + * + * @param includeParams {@code true} to include parameters in the profile; {@code false} + * otherwise. + * + * @return the corresponding sound profile if available; {@code null} if the none available. */ @NonNull - public List<SoundProfile> getAvailableSoundProfiles() { + public List<SoundProfile> getAvailableSoundProfiles(boolean includeParams) { try { - return mService.getAvailableSoundProfiles(mUserId); + return mService.getAvailableSoundProfiles(includeParams, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -441,7 +476,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public boolean setDefaultSoundProfile(@Nullable String id) { try { - return mService.setDefaultSoundProfile(id, mUserId); + return mService.setDefaultSoundProfile(id, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -450,7 +485,7 @@ public final class MediaQualityManager { /** * Gets all package names whose sound profiles are available. * - * @see #getSoundProfilesByPackage(String) + * @see #getSoundProfilesByPackage(String, boolean) * * @hide */ @@ -459,7 +494,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<String> getSoundProfilePackageNames() { try { - return mService.getSoundProfilePackageNames(mUserId); + return mService.getSoundProfilePackageNames(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -471,10 +506,12 @@ public final class MediaQualityManager { * * <p>If the profile is created successfully, * {@link SoundProfileCallback#onSoundProfileAdded(String, SoundProfile)} is invoked. + * + * @param sp the {@link SoundProfile} object to be created. */ public void createSoundProfile(@NonNull SoundProfile sp) { try { - mService.createSoundProfile(sp, mUserId); + mService.createSoundProfile(sp, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -483,10 +520,13 @@ public final class MediaQualityManager { /** * Updates an existing sound profile and store it in the system. + * + * @param profileId the id of the object to be updated. + * @param sp the {@link SoundProfile} object to be updated. */ public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) { try { - mService.updateSoundProfile(profileId, sp, mUserId); + mService.updateSoundProfile(profileId, sp, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -495,10 +535,12 @@ public final class MediaQualityManager { /** * Removes a sound profile from the system. + * + * @param profileId the id of the object to be removed. */ public void removeSoundProfile(@NonNull String profileId) { try { - mService.removeSoundProfile(profileId, mUserId); + mService.removeSoundProfile(profileId, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -510,7 +552,7 @@ public final class MediaQualityManager { @NonNull public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) { try { - return mService.getParamCapabilities(names, mUserId); + return mService.getParamCapabilities(names, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -528,7 +570,7 @@ public final class MediaQualityManager { @NonNull public List<String> getPictureProfileAllowList() { try { - return mService.getPictureProfileAllowList(mUserId); + return mService.getPictureProfileAllowList(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -542,7 +584,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setPictureProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setPictureProfileAllowList(packageNames, mUserId); + mService.setPictureProfileAllowList(packageNames, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -560,7 +602,7 @@ public final class MediaQualityManager { @NonNull public List<String> getSoundProfileAllowList() { try { - return mService.getSoundProfileAllowList(mUserId); + return mService.getSoundProfileAllowList(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -574,7 +616,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setSoundProfileAllowList(@NonNull List<String> packageNames) { try { - mService.setSoundProfileAllowList(packageNames, mUserId); + mService.setSoundProfileAllowList(packageNames, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -586,7 +628,7 @@ public final class MediaQualityManager { */ public boolean isSupported() { try { - return mService.isSupported(mUserId); + return mService.isSupported(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -604,7 +646,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setAutoPictureQualityEnabled(boolean enabled) { try { - mService.setAutoPictureQualityEnabled(enabled, mUserId); + mService.setAutoPictureQualityEnabled(enabled, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -615,7 +657,7 @@ public final class MediaQualityManager { */ public boolean isAutoPictureQualityEnabled() { try { - return mService.isAutoPictureQualityEnabled(mUserId); + return mService.isAutoPictureQualityEnabled(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -632,7 +674,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) public void setSuperResolutionEnabled(boolean enabled) { try { - mService.setSuperResolutionEnabled(enabled, mUserId); + mService.setSuperResolutionEnabled(enabled, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -643,7 +685,7 @@ public final class MediaQualityManager { */ public boolean isSuperResolutionEnabled() { try { - return mService.isSuperResolutionEnabled(mUserId); + return mService.isSuperResolutionEnabled(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -661,7 +703,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public void setAutoSoundQualityEnabled(boolean enabled) { try { - mService.setAutoSoundQualityEnabled(enabled, mUserId); + mService.setAutoSoundQualityEnabled(enabled, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -672,7 +714,7 @@ public final class MediaQualityManager { */ public boolean isAutoSoundQualityEnabled() { try { - return mService.isAutoSoundQualityEnabled(mUserId); + return mService.isAutoSoundQualityEnabled(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -721,7 +763,7 @@ public final class MediaQualityManager { @NonNull AmbientBacklightSettings settings) { Preconditions.checkNotNull(settings); try { - mService.setAmbientBacklightSettings(settings, mUserId); + mService.setAmbientBacklightSettings(settings, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -732,7 +774,7 @@ public final class MediaQualityManager { */ public boolean isAmbientBacklightEnabled() { try { - return mService.isAmbientBacklightEnabled(mUserId); + return mService.isAmbientBacklightEnabled(mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -746,7 +788,7 @@ public final class MediaQualityManager { @RequiresPermission(android.Manifest.permission.READ_COLOR_ZONES) public void setAmbientBacklightEnabled(boolean enabled) { try { - mService.setAmbientBacklightEnabled(enabled, mUserId); + mService.setAmbientBacklightEnabled(enabled, mUserHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -803,11 +845,11 @@ public final class MediaQualityManager { }); } - public void postError(int error) { + public void postError(String profileId, int error) { mExecutor.execute(new Runnable() { @Override public void run() { - mCallback.onError(error); + mCallback.onError(profileId, error); } }); } @@ -863,11 +905,11 @@ public final class MediaQualityManager { }); } - public void postError(int error) { + public void postError(String profileId, int error) { mExecutor.execute(new Runnable() { @Override public void run() { - mCallback.onError(error); + mCallback.onError(profileId, error); } }); } @@ -933,9 +975,11 @@ public final class MediaQualityManager { /** * This is invoked when an issue has occurred. * + * @param profileId the profile ID related to the error. {@code null} if there is no + * associated profile. * @param errorCode the error code */ - public void onError(@PictureProfile.ErrorCode int errorCode) { + public void onError(@Nullable String profileId, @PictureProfile.ErrorCode int errorCode) { } /** @@ -988,9 +1032,11 @@ public final class MediaQualityManager { /** * This is invoked when an issue has occurred. * + * @param profileId the profile ID related to the error. {@code null} if there is no + * associated profile. * @param errorCode the error code */ - public void onError(@SoundProfile.ErrorCode int errorCode) { + public void onError(@Nullable String profileId, @SoundProfile.ErrorCode int errorCode) { } /** @@ -1016,4 +1062,86 @@ public final class MediaQualityManager { public void onAmbientBacklightEvent(@NonNull AmbientBacklightEvent event) { } } + + /** + * Listener used to monitor status of active pictures. + */ + public interface ActiveProcessingPictureListener { + /** + * Called when active pictures are changed. + * + * @param activeProcessingPictures contents currently undergoing picture processing. + */ + void onActiveProcessingPicturesChanged( + @NonNull List<ActiveProcessingPicture> activeProcessingPictures); + } + + /** + * Adds an active picture listener for the contents owner by the caller. + */ + public void addActiveProcessingPictureListener( + @CallbackExecutor @NonNull Executor executor, + @NonNull ActiveProcessingPictureListener listener) { + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(executor); + synchronized (mLock) { + mApListenerRecords.add( + new ActiveProcessingPictureListenerRecord(listener, executor, false)); + } + } + + /** + * Adds an active picture listener for all contents. + * + * @hide + */ + @SuppressLint("PairedRegistration") + @SystemApi + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE) + public void addGlobalActiveProcessingPictureListener( + @NonNull Executor executor, + @NonNull ActiveProcessingPictureListener listener) { + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(executor); + synchronized (mLock) { + mApListenerRecords.add( + new ActiveProcessingPictureListenerRecord(listener, executor, true)); + } + } + + + /** + * Removes an active picture listener for the contents. + */ + public void removeActiveProcessingPictureListener( + @NonNull ActiveProcessingPictureListener listener) { + Preconditions.checkNotNull(listener); + synchronized (mLock) { + for (Iterator<ActiveProcessingPictureListenerRecord> it = mApListenerRecords.iterator(); + it.hasNext(); ) { + ActiveProcessingPictureListenerRecord record = it.next(); + if (record.getListener() == listener) { + it.remove(); + break; + } + } + } + } + + private static final class ActiveProcessingPictureListenerRecord { + private final ActiveProcessingPictureListener mListener; + private final Executor mExecutor; + private final boolean mIsGlobal; + + ActiveProcessingPictureListenerRecord( + ActiveProcessingPictureListener listener, Executor executor, boolean isGlobal) { + mListener = listener; + mExecutor = executor; + mIsGlobal = isGlobal; + } + + public ActiveProcessingPictureListener getListener() { + return mListener; + } + } } diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java index 714fd36d664a..d9d21932d09a 100644 --- a/media/java/android/media/quality/PictureProfileHandle.java +++ b/media/java/android/media/quality/PictureProfileHandle.java @@ -28,11 +28,14 @@ import android.os.Parcelable; * A picture profile represents a collection of parameters used to configure picture processing * to enhance the quality of graphic buffers. * + * @see PictureProfile.getHandle + * * @hide */ @SystemApi @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES) public final class PictureProfileHandle implements Parcelable { + /** A handle that represents no picture processing configuration. */ public static final @NonNull PictureProfileHandle NONE = new PictureProfileHandle(0); private final long mId; @@ -42,7 +45,16 @@ public final class PictureProfileHandle implements Parcelable { mId = id; } - /** @hide */ + /** + * An ID that uniquely identifies the picture profile across the system. + * + * This ID can be used to construct an NDK PictureProfileHandle to be fed directly into + * IGraphicBufferProducer to couple a picture profile to a graphic buffer. + * + * Note: These IDs are generated randomly and are not stable across reboots. + * + * @hide + */ @SystemApi @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES) public long getId() { diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp index 532213611cf1..074973188c66 100644 --- a/native/android/dynamic_instrumentation_manager.cpp +++ b/native/android/dynamic_instrumentation_manager.cpp @@ -15,7 +15,9 @@ */ #define LOG_TAG "ADynamicInstrumentationManager" +#include <android-base/properties.h> #include <android/dynamic_instrumentation_manager.h> +#include <android/os/instrumentation/BnOffsetCallback.h> #include <android/os/instrumentation/ExecutableMethodFileOffsets.h> #include <android/os/instrumentation/IDynamicInstrumentationManager.h> #include <android/os/instrumentation/MethodDescriptor.h> @@ -23,7 +25,9 @@ #include <binder/Binder.h> #include <binder/IServiceManager.h> #include <utils/Log.h> +#include <utils/StrongPointer.h> +#include <future> #include <mutex> #include <optional> #include <string> @@ -31,6 +35,9 @@ namespace android::dynamicinstrumentationmanager { +using android::os::instrumentation::BnOffsetCallback; +using android::os::instrumentation::ExecutableMethodFileOffsets; + // Global instance of IDynamicInstrumentationManager, service is obtained only on first use. static std::mutex mLock; static sp<os::instrumentation::IDynamicInstrumentationManager> mService; @@ -131,6 +138,30 @@ void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy( delete instance; } +class ResultCallback : public BnOffsetCallback { +public: + ::android::binder::Status onResult( + const ::std::optional<ExecutableMethodFileOffsets>& offsets) override { + promise_.set_value(offsets); + return android::binder::Status::ok(); + } + + std::optional<ExecutableMethodFileOffsets> waitForResult() { + std::future<std::optional<ExecutableMethodFileOffsets>> futureResult = + promise_.get_future(); + auto futureStatus = futureResult.wait_for( + std::chrono::seconds(1 * android::base::HwTimeoutMultiplier())); + if (futureStatus == std::future_status::ready) { + return futureResult.get(); + } else { + return std::nullopt; + } + } + +private: + std::promise<std::optional<ExecutableMethodFileOffsets>> promise_; +}; + int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( const ADynamicInstrumentationManager_TargetProcess* targetProcess, const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor, @@ -150,15 +181,15 @@ int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( return INVALID_OPERATION; } - std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets; + android::sp<ResultCallback> resultCallback = android::sp<ResultCallback>::make(); binder_status_t result = service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel, - &offsets) + resultCallback) .exceptionCode(); if (result != OK) { return result; } - + std::optional<ExecutableMethodFileOffsets> offsets = resultCallback->waitForResult(); if (offsets != std::nullopt) { auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets(); value->containerPath = offsets->containerPath; @@ -170,4 +201,4 @@ int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets( } return result; -}
\ No newline at end of file +} diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 8dd8830cb490..1ccadf90c2a9 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -393,6 +393,7 @@ LIBANDROID { APerformanceHint_createSessionUsingConfig; # introduced=36 APerformanceHint_notifyWorkloadIncrease; # introduced=36 APerformanceHint_notifyWorkloadReset; # introduced=36 + APerformanceHint_notifyWorkloadSpike; # introduced=36 APerformanceHint_borrowSessionFromJava; # introduced=36 APerformanceHint_setNativeSurfaces; # introduced=36 AWorkDuration_create; # introduced=VanillaIceCream diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 608c01caee96..1945d90568b3 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -214,6 +214,7 @@ public: int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName); int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName); int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName); + int notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName); int setThreads(const int32_t* threadIds, size_t size); int getThreadIds(int32_t* const threadIds, size_t* size); int setPreferPowerEfficiency(bool enabled); @@ -328,7 +329,7 @@ APerformanceHintManager* APerformanceHintManager::create(std::shared_ptr<IHintMa bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) { mHintBudget = - std::max(kMaxLoadHintsPerInterval, + std::min(kMaxLoadHintsPerInterval, mHintBudget + static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate); mLastBudgetReplenish = now; @@ -600,6 +601,19 @@ int APerformanceHintSession::notifyWorkloadReset(bool cpu, bool gpu, const char* return sendHints(hints, now, debugName); } +int APerformanceHintSession::notifyWorkloadSpike(bool cpu, bool gpu, const char* debugName) { + std::vector<hal::SessionHint> hints(2); + hints.clear(); + if (cpu) { + hints.push_back(hal::SessionHint::CPU_LOAD_SPIKE); + } + if (gpu) { + hints.push_back(hal::SessionHint::GPU_LOAD_SPIKE); + } + int64_t now = ::android::uptimeNanos(); + return sendHints(hints, now, debugName); +} + int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) { if (size == 0) { ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__); @@ -1149,6 +1163,16 @@ int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool return session->notifyWorkloadReset(cpu, gpu, debugName); } +int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool cpu, bool gpu, + const char* debugName) { + VALIDATE_PTR(session) + VALIDATE_PTR(debugName) + if (!useNewLoadHintBehavior()) { + return ENOTSUP; + } + return session->notifyWorkloadReset(cpu, gpu, debugName); +} + int APerformanceHint_setNativeSurfaces(APerformanceHintSession* session, ANativeWindow** nativeWindows, int nativeWindowsSize, ASurfaceControl** surfaceControls, int surfaceControlsSize) { diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index b8f574f44338..c166e738ffb2 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -299,6 +299,10 @@ TEST_F(PerformanceHintTest, TestSession) { EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1)); result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint"); EXPECT_EQ(0, result); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_SPIKE))).Times(Exactly(1)); + EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_SPIKE))).Times(Exactly(1)); + result = APerformanceHint_notifyWorkloadSpike(session, true, true, "Test hint"); + EXPECT_EQ(0, result); result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1)); EXPECT_EQ(EINVAL, result); diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java index 560e7519de5e..8b8ab587e84a 100644 --- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java @@ -770,19 +770,19 @@ public class PackageWatchdog { * The minimum value that can be returned by any observer. * It represents that no mitigations were available. */ - public static final int LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT = + public static final int USER_IMPACT_THRESHOLD_NONE = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; /** * The mitigation impact beyond which the user will start noticing the mitigations. */ - public static final int MEDIUM_USER_IMPACT_THRESHOLD = + public static final int USER_IMPACT_THRESHOLD_MEDIUM = PackageHealthObserverImpact.USER_IMPACT_LEVEL_20; /** * The mitigation impact beyond which the user impact is severely high. */ - public static final int HIGH_USER_IMPACT_THRESHOLD = + public static final int USER_IMPACT_THRESHOLD_HIGH = PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; /** @@ -827,11 +827,9 @@ public class PackageWatchdog { public interface PackageHealthObserver { /** * Called when health check fails for the {@code versionedPackage}. - * - * Note: if the returned user impact is higher than - * {@link #DEFAULT_HIGH_USER_IMPACT_THRESHOLD}, then - * {@link #onExecuteHealthCheckMitigation} would be called only in severe device conditions - * like boot-loop or network failure. + * Note: if the returned user impact is higher than {@link #USER_IMPACT_THRESHOLD_HIGH}, + * then {@link #onExecuteHealthCheckMitigation} would be called only in severe device + * conditions like boot-loop or network failure. * * @param versionedPackage the package that is failing. This may be null if a native * service is crashing. @@ -839,9 +837,9 @@ public class PackageWatchdog { * @param mitigationCount the number of times mitigation has been called for this package * (including this time). * - * - * @return any value greater than {@link #LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT} to express - * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation} + * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express + * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}. + * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available. */ @PackageHealthObserverImpact int onHealthCheckFailed( @Nullable VersionedPackage versionedPackage, @@ -871,8 +869,9 @@ public class PackageWatchdog { * @param mitigationCount the number of times mitigation has been attempted for this * boot loop (including this time). * - * @return any value greater than {@link #LEAST_PACKAGE_HEALTH_OBSERVER_IMPACT} to express - * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation} + * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express + * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}. + * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available. */ default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) { return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; diff --git a/packages/NeuralNetworks/framework/Android.bp b/packages/NeuralNetworks/framework/Android.bp new file mode 100644 index 000000000000..6f45daae0802 --- /dev/null +++ b/packages/NeuralNetworks/framework/Android.bp @@ -0,0 +1,30 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +filegroup { + name: "framework-ondeviceintelligence-sources", + srcs: [ + "java/**/*.aidl", + "java/**/*.java", + ], + path: "java", + visibility: [ + "//frameworks/base:__subpackages__", + "//packages/modules/NeuralNetworks:__subpackages__", + ], +} diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java index 30c6e1924942..95fb2888a3e9 100644 --- a/core/java/android/app/ondeviceintelligence/DownloadCallback.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java @@ -23,8 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.PersistableBundle; - -import androidx.annotation.IntDef; +import android.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -53,14 +52,14 @@ public interface DownloadCallback { /** * Sent when feature download has been initiated already, hence no need to request download - * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if + * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check if * download has been completed. */ int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; /** * Sent when feature download did not start due to errors (e.g. remote exception of features not - * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check + * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureDetails} to check * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}. */ int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; @@ -72,7 +71,7 @@ public interface DownloadCallback { DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE, DOWNLOAD_FAILURE_STATUS_DOWNLOADING, DOWNLOAD_FAILURE_STATUS_UNAVAILABLE - }, open = true) + }) @Retention(RetentionPolicy.SOURCE) @interface DownloadFailureStatus { } diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl index 18494d754674..47cfb4a60dc4 100644 --- a/core/java/android/app/ondeviceintelligence/Feature.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl @@ -19,4 +19,4 @@ package android.app.ondeviceintelligence; /** * @hide */ -parcelable Feature; +@JavaOnlyStableParcelable parcelable Feature; diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java index bcc56073e51c..88f4de2989e4 100644 --- a/core/java/android/app/ondeviceintelligence/Feature.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java @@ -26,6 +26,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; +import java.util.Objects; + /** * Represents a typical feature associated with on-device intelligence. * @@ -56,9 +58,8 @@ public final class Feature implements Parcelable { this.mModelName = modelName; this.mType = type; this.mVariant = variant; - this.mFeatureParams = featureParams; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mFeatureParams); + this.mFeatureParams = Objects.requireNonNull(featureParams, + "featureParams should be non-null."); } /** Returns the unique and immutable identifier of this feature. */ @@ -167,8 +168,6 @@ public final class Feature implements Parcelable { this.mType = type; this.mVariant = variant; this.mFeatureParams = featureParams; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mFeatureParams); } public static final @NonNull Parcelable.Creator<Feature> CREATOR @@ -200,6 +199,7 @@ public final class Feature implements Parcelable { /** * Provides a builder instance to create a feature for given id. + * * @param id the unique identifier for the feature. */ public Builder(int id) { diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl index 0589bf8bacb9..c5b3532796cd 100644 --- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl @@ -19,4 +19,4 @@ package android.app.ondeviceintelligence; /** * @hide */ -parcelable FeatureDetails; +@JavaOnlyStableParcelable parcelable FeatureDetails; diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java index 44930f2c34f4..063cfb8c321e 100644 --- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java @@ -19,18 +19,18 @@ package android.app.ondeviceintelligence; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcelable; import android.os.PersistableBundle; -import androidx.annotation.IntDef; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.text.MessageFormat; +import java.util.Objects; /** * Represents a status of a requested {@link Feature}. @@ -69,7 +69,7 @@ public final class FeatureDetails implements Parcelable { FEATURE_STATUS_DOWNLOADING, FEATURE_STATUS_AVAILABLE, FEATURE_STATUS_SERVICE_UNAVAILABLE - }, open = true) + }) @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) public @interface Status { @@ -79,18 +79,12 @@ public final class FeatureDetails implements Parcelable { @Status int featureStatus, @NonNull PersistableBundle featureDetailParams) { this.mFeatureStatus = featureStatus; - com.android.internal.util.AnnotationValidations.validate( - Status.class, null, mFeatureStatus); - this.mFeatureDetailParams = featureDetailParams; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mFeatureDetailParams); + this.mFeatureDetailParams = Objects.requireNonNull(featureDetailParams); } public FeatureDetails( @Status int featureStatus) { this.mFeatureStatus = featureStatus; - com.android.internal.util.AnnotationValidations.validate( - Status.class, null, mFeatureStatus); this.mFeatureDetailParams = new PersistableBundle(); } @@ -155,11 +149,7 @@ public final class FeatureDetails implements Parcelable { PersistableBundle.CREATOR); this.mFeatureStatus = status; - com.android.internal.util.AnnotationValidations.validate( - Status.class, null, mFeatureStatus); this.mFeatureDetailParams = persistableBundle; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mFeatureDetailParams); } diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl new file mode 100644 index 000000000000..1fe201f8f1f8 --- /dev/null +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +/** + * @hide + */ +oneway interface ICancellationSignal { + void cancel(); +} diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl index 2d7ea1a7b016..2d7ea1a7b016 100644 --- a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IDownloadCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl index 2e056926e400..2e056926e400 100644 --- a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl index 8688028743d7..8688028743d7 100644 --- a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl index 7e5eb57bbc4a..7e5eb57bbc4a 100644 --- a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl index 1977a3923578..fac5ec6064f8 100644 --- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl @@ -16,8 +16,8 @@ package android.app.ondeviceintelligence; - import com.android.internal.infra.AndroidFuture; - import android.os.ICancellationSignal; + import com.android.modules.utils.AndroidFuture; + import android.app.ondeviceintelligence.ICancellationSignal; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl index 03946eebd40b..03946eebd40b 100644 --- a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IProcessingSignal.aidl diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl new file mode 100644 index 000000000000..6f07693dd39c --- /dev/null +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IRemoteCallback.aidl @@ -0,0 +1,24 @@ +/* +* Copyright 2024, The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package android.app.ondeviceintelligence; + +import android.os.Bundle; + +/* @hide */ +oneway interface IRemoteCallback { + void sendResult(in Bundle data); +} diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl index 270b600e2de5..270b600e2de5 100644 --- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IResponseCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl index 3e902405f3e0..3e902405f3e0 100644 --- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl index 958bef0a93e0..958bef0a93e0 100644 --- a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl index 6d70fc4577a2..6f6325408979 100644 --- a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl @@ -19,4 +19,4 @@ package android.app.ondeviceintelligence; /** * @hide */ -parcelable InferenceInfo; +@JavaOnlyStableParcelable parcelable InferenceInfo; diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java index cae8db27a435..cae8db27a435 100644 --- a/core/java/android/app/ondeviceintelligence/InferenceInfo.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java index 03ff563a88c0..2881c9d217dc 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java @@ -20,13 +20,14 @@ package android.app.ondeviceintelligence; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.PersistableBundle; -import androidx.annotation.IntDef; - import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** @@ -124,8 +125,9 @@ public class OnDeviceIntelligenceException extends Exception { PROCESSING_ERROR_SERVICE_UNAVAILABLE, ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, PROCESSING_UPDATE_STATUS_CONNECTION_FAILED - }, open = true) + }) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + @Retention(RetentionPolicy.SOURCE) @interface OnDeviceIntelligenceError { } diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java new file mode 100644 index 000000000000..7d35dd7f2237 --- /dev/null +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceFrameworkInitializer.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence; + +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; + +import android.annotation.FlaggedApi; +import android.annotation.SystemApi; +import android.app.SystemServiceRegistry; +import android.content.Context; + +/** + * Class for performing registration for OnDeviceIntelligence service. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public class OnDeviceIntelligenceFrameworkInitializer { + private OnDeviceIntelligenceFrameworkInitializer() { + } + + /** + * Called by {@link SystemServiceRegistry}'s static initializer and registers + * OnDeviceIntelligence service to {@link Context}, so that {@link Context#getSystemService} can + * return them. + * + * @throws IllegalStateException if this is called from anywhere besides {@link + * SystemServiceRegistry} + */ + public static void registerServiceWrappers() { + SystemServiceRegistry.registerContextAwareService(Context.ON_DEVICE_INTELLIGENCE_SERVICE, + OnDeviceIntelligenceManager.class, + (context, serviceBinder) -> { + IOnDeviceIntelligenceManager manager = + IOnDeviceIntelligenceManager.Stub.asInterface(serviceBinder); + return new OnDeviceIntelligenceManager(context, manager); + }); + } +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java index 91651e317b18..dc0665a5cea7 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -23,18 +23,18 @@ import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.CurrentTimeMillisLong; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.app.ondeviceintelligence.utils.BinderUtils; import android.content.Context; import android.graphics.Bitmap; -import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; -import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.PersistableBundle; import android.os.RemoteCallback; @@ -42,9 +42,7 @@ import android.os.RemoteException; import android.system.OsConstants; import android.util.Log; -import androidx.annotation.IntDef; - -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -80,10 +78,39 @@ public final class OnDeviceIntelligenceManager { public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY = "AugmentRequestContentBundleKey"; + /** + * Timeout to be used for unbinding to the configured remote {@link + * android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no requests in + * the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS = + "on_device_intelligence_unbind_timeout_ms"; + + /** + * Timeout that represents maximum idle time before which a callback should be populated. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS = + "on_device_intelligence_idle_timeout_ms"; + + /** + * Timeout to be used for unbinding to the configured remote {@link + * android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there are no + * requests in the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS = + "on_device_inference_unbind_timeout_ms"; + private static final String TAG = "OnDeviceIntelligence"; private final Context mContext; private final IOnDeviceIntelligenceManager mService; + /** * @hide */ @@ -105,11 +132,11 @@ public final class OnDeviceIntelligenceManager { try { RemoteCallback callback = new RemoteCallback(result -> { if (result == null) { - Binder.withCleanCallingIdentity( + BinderUtils.withCleanCallingIdentity( () -> callbackExecutor.execute(() -> versionConsumer.accept(0))); } long version = result.getLong(API_VERSION_BUNDLE_KEY); - Binder.withCleanCallingIdentity( + BinderUtils.withCleanCallingIdentity( () -> callbackExecutor.execute(() -> versionConsumer.accept(version))); }); mService.getVersion(callback); @@ -151,14 +178,14 @@ public final class OnDeviceIntelligenceManager { new IFeatureCallback.Stub() { @Override public void onSuccess(Feature result) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureReceiver.onResult(result))); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureReceiver.onError( new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); @@ -185,14 +212,14 @@ public final class OnDeviceIntelligenceManager { new IListFeaturesCallback.Stub() { @Override public void onSuccess(List<Feature> result) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureListReceiver.onResult(result))); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureListReceiver.onError( new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); @@ -223,14 +250,14 @@ public final class OnDeviceIntelligenceManager { @Override public void onSuccess(FeatureDetails result) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureDetailsReceiver.onResult(result))); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> featureDetailsReceiver.onError( new OnDeviceIntelligenceException(errorCode, errorMessage, errorParams)))); @@ -268,27 +295,27 @@ public final class OnDeviceIntelligenceManager { @Override public void onDownloadStarted(long bytesToDownload) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> callback.onDownloadStarted(bytesToDownload))); } @Override public void onDownloadProgress(long bytesDownloaded) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> callback.onDownloadProgress(bytesDownloaded))); } @Override public void onDownloadFailed(int failureStatus, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> callback.onDownloadFailed(failureStatus, errorMessage, errorParams))); } @Override public void onDownloadCompleted(PersistableBundle downloadParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> callback.onDownloadCompleted(downloadParams))); } }; @@ -325,14 +352,14 @@ public final class OnDeviceIntelligenceManager { ITokenInfoCallback callback = new ITokenInfoCallback.Stub() { @Override public void onSuccess(TokenInfo tokenInfo) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> outcomeReceiver.onResult(tokenInfo))); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> outcomeReceiver.onError( new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); @@ -377,7 +404,7 @@ public final class OnDeviceIntelligenceManager { IResponseCallback callback = new IResponseCallback.Stub() { @Override public void onSuccess(@InferenceParams Bundle result) { - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { callbackExecutor.execute(() -> processingCallback.onResult(result)); }); } @@ -385,7 +412,7 @@ public final class OnDeviceIntelligenceManager { @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> processingCallback.onError( new OnDeviceIntelligenceException( errorCode, errorMessage, errorParams)))); @@ -394,7 +421,7 @@ public final class OnDeviceIntelligenceManager { @Override public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request, @NonNull RemoteCallback contentCallback) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> processingCallback.onDataAugmentRequest(request, result -> { Bundle bundle = new Bundle(); bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result); @@ -447,7 +474,7 @@ public final class OnDeviceIntelligenceManager { IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() { @Override public void onNewContent(@InferenceParams Bundle result) { - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { callbackExecutor.execute( () -> streamingProcessingCallback.onPartialResult(result)); }); @@ -455,7 +482,7 @@ public final class OnDeviceIntelligenceManager { @Override public void onSuccess(@InferenceParams Bundle result) { - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { callbackExecutor.execute( () -> streamingProcessingCallback.onResult(result)); }); @@ -464,7 +491,7 @@ public final class OnDeviceIntelligenceManager { @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) { - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { callbackExecutor.execute( () -> streamingProcessingCallback.onError( new OnDeviceIntelligenceException( @@ -476,7 +503,7 @@ public final class OnDeviceIntelligenceManager { @Override public void onDataAugmentRequest(@NonNull @InferenceParams Bundle content, @NonNull RemoteCallback contentCallback) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> streamingProcessingCallback.onDataAugmentRequest(content, contentResponse -> { Bundle bundle = new Bundle(); @@ -537,7 +564,7 @@ public final class OnDeviceIntelligenceManager { REQUEST_TYPE_INFERENCE, REQUEST_TYPE_PREPARE, REQUEST_TYPE_EMBEDDINGS - }, open = true) + }) @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) @@ -614,8 +641,17 @@ public final class OnDeviceIntelligenceManager { if (error != null || cancellationTransport == null) { Log.e(TAG, "Unable to receive the remote cancellation signal.", error); } else { - cancellationSignal.setRemote( - ICancellationSignal.Stub.asInterface(cancellationTransport)); + ICancellationSignal remoteCancellationSignal = + ICancellationSignal.Stub.asInterface(cancellationTransport); + cancellationSignal.setOnCancelListener( + () -> { + try { + remoteCancellationSignal.cancel(); + } catch (RemoteException e) { + Log.w(TAG, "Unable to propagate cancellation signal.", + e); + } + }); } }, callbackExecutor); return cancellationFuture; @@ -638,6 +674,4 @@ public final class OnDeviceIntelligenceManager { }, executor); return processingSignalFuture; } - - -} +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java index e50d6b1fa97a..e50d6b1fa97a 100644 --- a/core/java/android/app/ondeviceintelligence/ProcessingCallback.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingCallback.java diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java index 733f4fad96f4..733f4fad96f4 100644 --- a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ProcessingSignal.java diff --git a/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java index 7ee2af7376ed..7ee2af7376ed 100644 --- a/core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl index 2c19c1eb246c..599b337fd20f 100644 --- a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl @@ -19,4 +19,4 @@ package android.app.ondeviceintelligence; /** * @hide */ -parcelable TokenInfo; +@JavaOnlyStableParcelable parcelable TokenInfo; diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java index 035cc4b365b5..035cc4b365b5 100644 --- a/core/java/android/app/ondeviceintelligence/TokenInfo.java +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.java diff --git a/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java new file mode 100644 index 000000000000..2916f030e3d0 --- /dev/null +++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/utils/BinderUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.ondeviceintelligence.utils; + +import android.annotation.NonNull; +import android.os.Binder; + +import java.util.function.Supplier; + +/** + * Collection of utilities for {@link Binder} and related classes. + * @hide + */ +public class BinderUtils { + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} + * + * Any exception thrown by the given action will be caught and rethrown after the call to + * {@link Binder#restoreCallingIdentity} + * + * Note that this is copied from Binder#withCleanCallingIdentity with minor changes + * since it is not public. + * + * @hide + */ + public static final <T extends Exception> void withCleanCallingIdentity( + @NonNull ThrowingRunnable<T> action) throws T { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + action.run(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * Like a Runnable, but declared to throw an exception. + * + * @param <T> The exception class which is declared to be thrown. + */ + @FunctionalInterface + public interface ThrowingRunnable<T extends Exception> { + /** @see java.lang.Runnable */ + void run() throws T; + } + + /** + * Convenience method for running the provided action enclosed in + * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity} returning the + * result. + * + * <p>Any exception thrown by the given action will be caught and rethrown after + * the call to {@link Binder#restoreCallingIdentity}. + * + * Note that this is copied from Binder#withCleanCallingIdentity with minor changes + * since it is not public. + * + * @hide + */ + public static final <T, E extends Exception> T withCleanCallingIdentity( + @NonNull ThrowingSupplier<T, E> action) throws E { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + return action.get(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + /** + * An equivalent of {@link Supplier} + * + * @param <T> The class which is declared to be returned. + * @param <E> The exception class which is declared to be thrown. + */ + @FunctionalInterface + public interface ThrowingSupplier<T, E extends Exception> { + /** @see java.util.function.Supplier */ + T get() throws E; + } +}
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl index 45c43502d6de..cba18c1ef36d 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl @@ -18,14 +18,14 @@ package android.service.ondeviceintelligence; import android.os.PersistableBundle; import android.os.ParcelFileDescriptor; -import android.os.ICancellationSignal; +import android.app.ondeviceintelligence.ICancellationSignal; import android.os.RemoteCallback; import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IListFeaturesCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import android.service.ondeviceintelligence.IRemoteProcessingService; diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl index 1af3b0f374f1..504fdd9b17f9 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl @@ -21,11 +21,11 @@ import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.IProcessingSignal; import android.app.ondeviceintelligence.Feature; -import android.os.IRemoteCallback; -import android.os.ICancellationSignal; +import android.app.ondeviceintelligence.IRemoteCallback; +import android.app.ondeviceintelligence.ICancellationSignal; import android.os.PersistableBundle; import android.os.Bundle; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import android.service.ondeviceintelligence.IRemoteStorageService; import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; diff --git a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl index 7ead8690abb4..7ead8690abb4 100644 --- a/core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl diff --git a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl index 32a8a6a70406..32a8a6a70406 100644 --- a/core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl index a6f49e17d80e..253df890b198 100644 --- a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl @@ -20,7 +20,7 @@ import android.app.ondeviceintelligence.Feature; import android.os.ParcelFileDescriptor; import android.os.RemoteCallback; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; /** * Interface for a concrete implementation to provide access to storage read access diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java index d82fe1ca885c..6907e2bdf2b3 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -18,8 +18,6 @@ package android.service.ondeviceintelligence; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; - import android.annotation.CallSuper; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; @@ -27,11 +25,11 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; -import android.annotation.TestApi; import android.app.Service; import android.app.ondeviceintelligence.DownloadCallback; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.FeatureDetails; +import android.app.ondeviceintelligence.ICancellationSignal; import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; @@ -39,14 +37,14 @@ import android.app.ondeviceintelligence.IListFeaturesCallback; import android.app.ondeviceintelligence.OnDeviceIntelligenceException; import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams; +import android.app.ondeviceintelligence.utils.BinderUtils; import android.content.Intent; -import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.ICancellationSignal; import android.os.Looper; +import android.os.Message; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; @@ -55,10 +53,11 @@ import android.os.RemoteException; import android.util.Log; import android.util.Slog; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; @@ -92,6 +91,18 @@ import java.util.function.LongConsumer; @SystemApi @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) public abstract class OnDeviceIntelligenceService extends Service { + private static final int MSG_ON_READY = 1; + private static final int MSG_GET_VERSION = 2; + private static final int MSG_LIST_FEATURES = 3; + private static final int MSG_GET_FEATURE = 4; + private static final int MSG_GET_FEATURE_DETAILS = 5; + private static final int MSG_DOWNLOAD_FEATURE = 6; + private static final int MSG_GET_READ_ONLY_FILE_DESCRIPTOR = 7; + private static final int MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP = 8; + private static final int MSG_REGISTER_REMOTE_SERVICES = 9; + private static final int MSG_INFERENCE_SERVICE_CONNECTED = 10; + private static final int MSG_INFERENCE_SERVICE_DISCONNECTED = 11; + private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName(); private volatile IRemoteProcessingService mRemoteProcessingService; @@ -101,19 +112,71 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void onCreate() { super.onCreate(); - mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */); + mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(@NonNull Message msg) { + switch (msg.what) { + case MSG_ON_READY: + OnDeviceIntelligenceService.this.onReady(); + break; + case MSG_GET_VERSION: + OnDeviceIntelligenceService.this.onGetVersion( + (LongConsumer) msg.obj); + break; + case MSG_LIST_FEATURES: + OnDeviceIntelligenceService.this.onListFeatures( + msg.arg1, + (OutcomeReceiver<List<Feature>, OnDeviceIntelligenceException>) msg.obj); + break; + case MSG_GET_FEATURE: + GetFeatureParams params = (GetFeatureParams) msg.obj; + OnDeviceIntelligenceService.this.onGetFeature( + msg.arg1, + msg.arg2, + params.callback); + break; + case MSG_GET_FEATURE_DETAILS: + FeatureDetailsParams detailsParams = (FeatureDetailsParams) msg.obj; + OnDeviceIntelligenceService.this.onGetFeatureDetails( + msg.arg1, + detailsParams.feature, + detailsParams.callback); + break; + case MSG_DOWNLOAD_FEATURE: + DownloadParams downloadParams = (DownloadParams) msg.obj; + OnDeviceIntelligenceService.this.onDownloadFeature( + msg.arg1, + downloadParams.feature, + downloadParams.cancellationSignal, + downloadParams.callback); + break; + case MSG_GET_READ_ONLY_FILE_DESCRIPTOR: + FileDescriptorParams fdParams = (FileDescriptorParams) msg.obj; + OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor( + fdParams.fileName, + fdParams.future); + break; + case MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP: + FeatureFileDescriptorParams ffdParams = + (FeatureFileDescriptorParams) msg.obj; + OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap( + ffdParams.feature, + ffdParams.consumer); + break; + case MSG_REGISTER_REMOTE_SERVICES: + mRemoteProcessingService = (IRemoteProcessingService) msg.obj; + break; + case MSG_INFERENCE_SERVICE_CONNECTED: + OnDeviceIntelligenceService.this.onInferenceServiceConnected(); + break; + case MSG_INFERENCE_SERVICE_DISCONNECTED: + OnDeviceIntelligenceService.this.onInferenceServiceDisconnected(); + break; + } + } + }; } - /** - * The {@link Intent} that must be declared as handled by the service. To be supported, the - * service must also require the - * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE} - * permission so that other applications can not abuse it. - */ - @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) - public static final String SERVICE_INTERFACE = - "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; - /** * @hide @@ -126,45 +189,37 @@ public abstract class OnDeviceIntelligenceService extends Service { /** {@inheritDoc} */ @Override public void ready() { - mHandler.executeOrSendMessage( - obtainMessage(OnDeviceIntelligenceService::onReady, - OnDeviceIntelligenceService.this)); + mHandler.sendEmptyMessage(MSG_ON_READY); } @Override public void getVersion(RemoteCallback remoteCallback) { Objects.requireNonNull(remoteCallback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onGetVersion, - OnDeviceIntelligenceService.this, l -> { - Bundle b = new Bundle(); - b.putLong( - OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, - l); - remoteCallback.sendResult(b); - })); + Message msg = Message.obtain(mHandler, MSG_GET_VERSION, + (LongConsumer) (l -> { + Bundle b = new Bundle(); + b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l); + remoteCallback.sendResult(b); + })); + mHandler.sendMessage(msg); } @Override public void listFeatures(int callerUid, IListFeaturesCallback listFeaturesCallback) { Objects.requireNonNull(listFeaturesCallback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onListFeatures, - OnDeviceIntelligenceService.this, callerUid, - wrapListFeaturesCallback(listFeaturesCallback))); + Message msg = Message.obtain(mHandler, MSG_LIST_FEATURES, + callerUid, 0, wrapListFeaturesCallback(listFeaturesCallback)); + mHandler.sendMessage(msg); } @Override public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) { Objects.requireNonNull(featureCallback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onGetFeature, - OnDeviceIntelligenceService.this, callerUid, - id, wrapFeatureCallback(featureCallback))); + Message msg = Message.obtain(mHandler, MSG_GET_FEATURE, + callerUid, id, + new GetFeatureParams(wrapFeatureCallback(featureCallback))); + mHandler.sendMessage(msg); } @@ -173,11 +228,11 @@ public abstract class OnDeviceIntelligenceService extends Service { IFeatureDetailsCallback featureDetailsCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(featureDetailsCallback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onGetFeatureDetails, - OnDeviceIntelligenceService.this, callerUid, - feature, wrapFeatureDetailsCallback(featureDetailsCallback))); + Message msg = Message.obtain(mHandler, MSG_GET_FEATURE_DETAILS, + new FeatureDetailsParams(feature, + wrapFeatureDetailsCallback(featureDetailsCallback))); + msg.arg1 = callerUid; + mHandler.sendMessage(msg); } @Override @@ -186,18 +241,24 @@ public abstract class OnDeviceIntelligenceService extends Service { IDownloadCallback downloadCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(downloadCallback); - ICancellationSignal transport = null; + + CancellationSignal cancellationSignal = new CancellationSignal(); if (cancellationSignalFuture != null) { - transport = CancellationSignal.createTransport(); + ICancellationSignal transport = new ICancellationSignal.Stub() { + @Override + public void cancel() { + cancellationSignal.cancel(); + } + }; cancellationSignalFuture.complete(transport); } - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onDownloadFeature, - OnDeviceIntelligenceService.this, callerUid, - feature, - CancellationSignal.fromTransport(transport), + + Message msg = Message.obtain(mHandler, MSG_DOWNLOAD_FEATURE, + new DownloadParams(feature, + cancellationSignalFuture != null ? cancellationSignal : null, wrapDownloadCallback(downloadCallback))); + msg.arg1 = callerUid; + mHandler.sendMessage(msg); } @Override @@ -205,11 +266,9 @@ public abstract class OnDeviceIntelligenceService extends Service { AndroidFuture<ParcelFileDescriptor> future) { Objects.requireNonNull(fileName); Objects.requireNonNull(future); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor, - OnDeviceIntelligenceService.this, fileName, - future)); + Message msg = Message.obtain(mHandler, MSG_GET_READ_ONLY_FILE_DESCRIPTOR, + new FileDescriptorParams(fileName, future)); + mHandler.sendMessage(msg); } @Override @@ -217,16 +276,15 @@ public abstract class OnDeviceIntelligenceService extends Service { Feature feature, RemoteCallback remoteCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(remoteCallback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap, - OnDeviceIntelligenceService.this, feature, - parcelFileDescriptorMap -> { - Bundle bundle = new Bundle(); - parcelFileDescriptorMap.forEach(bundle::putParcelable); - remoteCallback.sendResult(bundle); - tryClosePfds(parcelFileDescriptorMap.values()); - })); + Message msg = Message.obtain(mHandler, + MSG_GET_READ_ONLY_FEATURE_FILE_DESCRIPTOR_MAP, + new FeatureFileDescriptorParams(feature, parcelFileDescriptorMap -> { + Bundle bundle = new Bundle(); + parcelFileDescriptorMap.forEach(bundle::putParcelable); + remoteCallback.sendResult(bundle); + tryClosePfds(parcelFileDescriptorMap.values()); + })); + mHandler.sendMessage(msg); } @Override @@ -237,18 +295,12 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void notifyInferenceServiceConnected() { - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onInferenceServiceConnected, - OnDeviceIntelligenceService.this)); + mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_CONNECTED); } @Override public void notifyInferenceServiceDisconnected() { - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceIntelligenceService::onInferenceServiceDisconnected, - OnDeviceIntelligenceService.this)); + mHandler.sendEmptyMessage(MSG_INFERENCE_SERVICE_DISCONNECTED); } }; } @@ -257,13 +309,77 @@ public abstract class OnDeviceIntelligenceService extends Service { } /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the + * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE} + * permission so that other applications can not abuse it. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; + + // Parameter holder classes + private static class GetFeatureParams { + final OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback; + + GetFeatureParams(OutcomeReceiver<Feature, OnDeviceIntelligenceException> callback) { + this.callback = callback; + } + } + + private static class FeatureDetailsParams { + final Feature feature; + final OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback; + + FeatureDetailsParams(Feature feature, + OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceException> callback) { + this.feature = feature; + this.callback = callback; + } + } + + private static class DownloadParams { + final Feature feature; + final CancellationSignal cancellationSignal; + final DownloadCallback callback; + + DownloadParams(Feature feature, CancellationSignal cancellationSignal, + DownloadCallback callback) { + this.feature = feature; + this.cancellationSignal = cancellationSignal; + this.callback = callback; + } + } + + private static class FileDescriptorParams { + final String fileName; + final AndroidFuture<ParcelFileDescriptor> future; + + FileDescriptorParams(String fileName, AndroidFuture<ParcelFileDescriptor> future) { + this.fileName = fileName; + this.future = future; + } + } + + private static class FeatureFileDescriptorParams { + final Feature feature; + final Consumer<Map<String, ParcelFileDescriptor>> consumer; + + FeatureFileDescriptorParams(Feature feature, + Consumer<Map<String, ParcelFileDescriptor>> consumer) { + this.feature = feature; + this.consumer = consumer; + } + } + + /** * Using this signal to assertively a signal each time service binds successfully, used only in * tests to get a signal that service instance is ready. This is needed because we cannot rely * on {@link #onCreate} or {@link #onBind} to be invoke on each binding. * * @hide */ - @TestApi + @SystemApi public void onReady() { } @@ -306,7 +422,7 @@ public abstract class OnDeviceIntelligenceService extends Service { new IProcessingUpdateStatusCallback.Stub() { @Override public void onSuccess(PersistableBundle result) { - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { callbackExecutor.execute( () -> statusReceiver.onResult(result)); }); @@ -314,7 +430,7 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void onFailure(int errorCode, String errorMessage) { - Binder.withCleanCallingIdentity(() -> callbackExecutor.execute( + BinderUtils.withCleanCallingIdentity(() -> callbackExecutor.execute( () -> statusReceiver.onError( new OnDeviceIntelligenceException( errorCode, errorMessage)))); @@ -459,7 +575,7 @@ public abstract class OnDeviceIntelligenceService extends Service { private void onGetReadOnlyFileDescriptor(@NonNull String fileName, @NonNull AndroidFuture<ParcelFileDescriptor> future) { Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName); - Binder.withCleanCallingIdentity(() -> { + BinderUtils.withCleanCallingIdentity(() -> { Slog.v(TAG, "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage."); File f = new File(getBaseContext().getFilesDir(), fileName); @@ -476,7 +592,11 @@ public abstract class OnDeviceIntelligenceService extends Service { } finally { future.complete(pfd); if (pfd != null) { - pfd.close(); + try { + pfd.close(); + } catch (IOException e) { + Log.w(TAG, "Error closing FD", e); + } } } }); diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 3181556eded7..315dbaf919e5 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -19,10 +19,8 @@ package android.service.ondeviceintelligence; import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; - -import android.annotation.CallbackExecutor; import android.annotation.CallSuper; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,7 +29,9 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.ICancellationSignal; import android.app.ondeviceintelligence.IProcessingSignal; +import android.app.ondeviceintelligence.IRemoteCallback; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; @@ -48,11 +48,9 @@ import android.content.Intent; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; -import android.os.ICancellationSignal; -import android.os.IRemoteCallback; import android.os.Looper; +import android.os.Message; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; @@ -61,7 +59,8 @@ import android.os.RemoteException; import android.util.Log; import android.util.Slog; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; +import com.android.modules.utils.HandlerExecutor; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -100,6 +99,12 @@ import java.util.function.Consumer; public abstract class OnDeviceSandboxedInferenceService extends Service { private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName(); + private static final int MSG_TOKEN_INFO_REQUEST = 1; + private static final int MSG_PROCESS_REQUEST_STREAMING = 2; + private static final int MSG_PROCESS_REQUEST = 3; + private static final int MSG_UPDATE_PROCESSING_STATE = 4; + + /** * @hide */ @@ -133,12 +138,12 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { * @hide */ public static final String MODEL_LOADED_BROADCAST_INTENT = - "android.service.ondeviceintelligence.MODEL_LOADED"; + "android.service.ondeviceintelligence.MODEL_LOADED"; /** * @hide */ public static final String MODEL_UNLOADED_BROADCAST_INTENT = - "android.service.ondeviceintelligence.MODEL_UNLOADED"; + "android.service.ondeviceintelligence.MODEL_UNLOADED"; /** * @hide @@ -152,12 +157,115 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onCreate() { super.onCreate(); - mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */); + mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TOKEN_INFO_REQUEST: + TokenInfoParams params = (TokenInfoParams) msg.obj; + OnDeviceSandboxedInferenceService.this.onTokenInfoRequest( + msg.arg1, + params.feature, + params.request, + params.cancellationSignal, + params.callback); + break; + case MSG_PROCESS_REQUEST_STREAMING: + StreamingRequestParams streamParams = (StreamingRequestParams) msg.obj; + OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming( + msg.arg1, + streamParams.feature, + streamParams.request, + msg.arg2, + streamParams.cancellationSignal, + streamParams.processingSignal, + streamParams.callback); + break; + case MSG_PROCESS_REQUEST: + RequestParams requestParams = (RequestParams) msg.obj; + OnDeviceSandboxedInferenceService.this.onProcessRequest( + msg.arg1, + requestParams.feature, + requestParams.request, + msg.arg2, + requestParams.cancellationSignal, + requestParams.processingSignal, + requestParams.callback); + break; + case MSG_UPDATE_PROCESSING_STATE: + UpdateStateParams stateParams = (UpdateStateParams) msg.obj; + OnDeviceSandboxedInferenceService.this.onUpdateProcessingState( + stateParams.processingState, + stateParams.callback); + break; + } + } + }; + } + + // Parameter holder classes + private static class TokenInfoParams { + final Feature feature; + final Bundle request; + final CancellationSignal cancellationSignal; + final OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback; + + TokenInfoParams(Feature feature, Bundle request, CancellationSignal cancellationSignal, + OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> callback) { + this.feature = feature; + this.request = request; + this.cancellationSignal = cancellationSignal; + this.callback = callback; + } + } + + private static class StreamingRequestParams { + final Feature feature; + final Bundle request; + final CancellationSignal cancellationSignal; + final ProcessingSignal processingSignal; + final StreamingProcessingCallback callback; + + StreamingRequestParams(Feature feature, Bundle request, + CancellationSignal cancellationSignal, ProcessingSignal processingSignal, + StreamingProcessingCallback callback) { + this.feature = feature; + this.request = request; + this.cancellationSignal = cancellationSignal; + this.processingSignal = processingSignal; + this.callback = callback; + } + } + + private static class RequestParams { + final Feature feature; + final Bundle request; + final CancellationSignal cancellationSignal; + final ProcessingSignal processingSignal; + final ProcessingCallback callback; + + RequestParams(Feature feature, Bundle request, + CancellationSignal cancellationSignal, ProcessingSignal processingSignal, + ProcessingCallback callback) { + this.feature = feature; + this.request = request; + this.cancellationSignal = cancellationSignal; + this.processingSignal = processingSignal; + this.callback = callback; + } + } + + private static class UpdateStateParams { + final Bundle processingState; + final OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback; + + UpdateStateParams(Bundle processingState, + OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> callback) { + this.processingState = processingState; + this.callback = callback; + } } - /** - * @hide - */ @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { @@ -168,8 +276,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { IRemoteCallback remoteCallback) throws RemoteException { Objects.requireNonNull(storageService); mRemoteStorageService = storageService; - remoteCallback.sendResult( - Bundle.EMPTY); //to notify caller uid to system-server. + remoteCallback.sendResult(Bundle.EMPTY); } @Override @@ -178,34 +285,42 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { ITokenInfoCallback tokenInfoCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(tokenInfoCallback); - ICancellationSignal transport = null; + CancellationSignal cancellationSignal = new CancellationSignal(); if (cancellationSignalFuture != null) { - transport = CancellationSignal.createTransport(); + ICancellationSignal transport = new ICancellationSignal.Stub() { + @Override + public void cancel() { + cancellationSignal.cancel(); + } + }; cancellationSignalFuture.complete(transport); } - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceSandboxedInferenceService::onTokenInfoRequest, - OnDeviceSandboxedInferenceService.this, - callerUid, feature, - request, - CancellationSignal.fromTransport(transport), + Message msg = Message.obtain(mHandler, MSG_TOKEN_INFO_REQUEST, + callerUid, 0, + new TokenInfoParams(feature, request, + cancellationSignalFuture != null ? cancellationSignal : null, wrapTokenInfoCallback(tokenInfoCallback))); + mHandler.sendMessage(msg); } @Override - public void processRequestStreaming(int callerUid, Feature feature, Bundle request, - int requestType, + public void processRequestStreaming(int callerUid, Feature feature, + Bundle request, int requestType, AndroidFuture cancellationSignalFuture, AndroidFuture processingSignalFuture, IStreamingResponseCallback callback) { Objects.requireNonNull(feature); Objects.requireNonNull(callback); - ICancellationSignal transport = null; + CancellationSignal cancellationSignal = new CancellationSignal(); if (cancellationSignalFuture != null) { - transport = CancellationSignal.createTransport(); + ICancellationSignal transport = new ICancellationSignal.Stub() { + @Override + public void cancel() { + cancellationSignal.cancel(); + } + }; cancellationSignalFuture.complete(transport); } IProcessingSignal processingSignalTransport = null; @@ -214,30 +329,32 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { processingSignalFuture.complete(processingSignalTransport); } - - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceSandboxedInferenceService::onProcessRequestStreaming, - OnDeviceSandboxedInferenceService.this, callerUid, - feature, - request, - requestType, - CancellationSignal.fromTransport(transport), + Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST_STREAMING, + callerUid, requestType, + new StreamingRequestParams(feature, request, + cancellationSignalFuture != null ? cancellationSignal : null, ProcessingSignal.fromTransport(processingSignalTransport), wrapStreamingResponseCallback(callback))); + mHandler.sendMessage(msg); } @Override - public void processRequest(int callerUid, Feature feature, Bundle request, - int requestType, + public void processRequest(int callerUid, Feature feature, + Bundle request, int requestType, AndroidFuture cancellationSignalFuture, AndroidFuture processingSignalFuture, IResponseCallback callback) { Objects.requireNonNull(feature); Objects.requireNonNull(callback); - ICancellationSignal transport = null; + + CancellationSignal cancellationSignal = new CancellationSignal(); if (cancellationSignalFuture != null) { - transport = CancellationSignal.createTransport(); + ICancellationSignal transport = new ICancellationSignal.Stub() { + @Override + public void cancel() { + cancellationSignal.cancel(); + } + }; cancellationSignalFuture.complete(transport); } IProcessingSignal processingSignalTransport = null; @@ -245,14 +362,14 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { processingSignalTransport = ProcessingSignal.createTransport(); processingSignalFuture.complete(processingSignalTransport); } - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceSandboxedInferenceService::onProcessRequest, - OnDeviceSandboxedInferenceService.this, callerUid, feature, - request, requestType, - CancellationSignal.fromTransport(transport), + + Message msg = Message.obtain(mHandler, MSG_PROCESS_REQUEST, + callerUid, requestType, + new RequestParams(feature, request, + cancellationSignalFuture != null ? cancellationSignal : null, ProcessingSignal.fromTransport(processingSignalTransport), wrapResponseCallback(callback))); + mHandler.sendMessage(msg); } @Override @@ -260,11 +377,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { IProcessingUpdateStatusCallback callback) { Objects.requireNonNull(processingState); Objects.requireNonNull(callback); - mHandler.executeOrSendMessage( - obtainMessage( - OnDeviceSandboxedInferenceService::onUpdateProcessingState, - OnDeviceSandboxedInferenceService.this, processingState, + + Message msg = Message.obtain(mHandler, MSG_UPDATE_PROCESSING_STATE, + new UpdateStateParams(processingState, wrapOutcomeReceiver(callback))); + mHandler.sendMessage(msg); } }; } @@ -471,7 +588,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { IResponseCallback callback) { return new ProcessingCallback() { @Override - public void onResult(@androidx.annotation.NonNull Bundle result) { + public void onResult(@NonNull Bundle result) { try { callback.onSuccess(result); } catch (RemoteException e) { @@ -507,7 +624,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { IStreamingResponseCallback callback) { return new StreamingProcessingCallback() { @Override - public void onPartialResult(@androidx.annotation.NonNull Bundle partialResult) { + public void onPartialResult(@NonNull Bundle partialResult) { try { callback.onNewContent(partialResult); } catch (RemoteException e) { @@ -516,7 +633,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } @Override - public void onResult(@androidx.annotation.NonNull Bundle result) { + public void onResult(@NonNull Bundle result) { try { callback.onSuccess(result); } catch (RemoteException e) { @@ -549,7 +666,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { } private RemoteCallback wrapRemoteCallback( - @androidx.annotation.NonNull Consumer<Bundle> contentCallback) { + @NonNull Consumer<Bundle> contentCallback) { return new RemoteCallback( result -> { if (result != null) { @@ -604,7 +721,7 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { @Override public void onError( - @androidx.annotation.NonNull OnDeviceIntelligenceException error) { + @NonNull OnDeviceIntelligenceException error) { try { callback.onFailure(error.getErrorCode(), error.getMessage()); } catch (RemoteException e) { diff --git a/packages/NeuralNetworks/service/Android.bp b/packages/NeuralNetworks/service/Android.bp new file mode 100644 index 000000000000..05c603f5ebce --- /dev/null +++ b/packages/NeuralNetworks/service/Android.bp @@ -0,0 +1,29 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +filegroup { + name: "service-ondeviceintelligence-sources", + srcs: [ + "java/**/*.java", + ], + path: "java", + visibility: [ + "//frameworks/base:__subpackages__", + "//packages/modules/NeuralNetworks:__subpackages__", + ], +} diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java index 7dd8f2fdcecb..2626cc880e09 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java @@ -21,6 +21,7 @@ import static android.system.OsConstants.O_ACCMODE; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.PROT_READ; +import android.annotation.SuppressLint; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; @@ -42,7 +43,7 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; @@ -50,6 +51,8 @@ import java.util.concurrent.TimeoutException; /** * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to * some known types. + * + * @hide */ public class BundleUtil { private static final String TAG = "BundleUtil"; @@ -76,7 +79,7 @@ public class BundleUtil { * {@link ClassNotFoundException} exception is swallowed and `null` is returned * instead. We want to ensure cleanup of null entries in such case. */ - bundle.putObject(key, null); + bundle.putParcelable(key, null); continue; } if (canMarshall(obj) || obj instanceof CursorWindow) { @@ -122,7 +125,7 @@ public class BundleUtil { * {@link ClassNotFoundException} exception is swallowed and `null` is returned * instead. We want to ensure cleanup of null entries in such case. */ - bundle.putObject(key, null); + bundle.putParcelable(key, null); continue; } if (canMarshall(obj)) { @@ -167,7 +170,7 @@ public class BundleUtil { * {@link ClassNotFoundException} exception is swallowed and `null` is returned * instead. We want to ensure cleanup of null entries in such case. */ - bundle.putObject(key, null); + bundle.putParcelable(key, null); continue; } if (canMarshall(obj)) { @@ -317,11 +320,16 @@ public class BundleUtil { }; } - private static boolean canMarshall(Object obj) { - return obj instanceof byte[] || obj instanceof PersistableBundle - || PersistableBundle.isValidType(obj); + private static boolean canMarshall(Object value) { + return (value instanceof byte[]) || (value instanceof Integer) || (value instanceof Long) || + (value instanceof Double) || (value instanceof String) || + (value instanceof int[]) || (value instanceof long[]) || + (value instanceof double[]) || (value instanceof String[]) || + (value instanceof PersistableBundle) || (value == null) || + (value instanceof Boolean) || (value instanceof boolean[]); } + @SuppressLint("NewApi") private static void ensureValidBundle(Bundle bundle) { if (bundle == null) { throw new IllegalArgumentException("Request passed is expected to be non-null"); @@ -364,7 +372,7 @@ public class BundleUtil { } } catch (ErrnoException e) { throw new BadParcelableException( - "Invalid File descriptor passed in the Bundle.", e); + "Invalid File descriptor passed in the Bundle."); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java index bef3f8048da1..e8a1b322f661 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java @@ -28,6 +28,9 @@ import java.util.Comparator; import java.util.List; import java.util.TreeSet; +/** + * @hide + */ public class InferenceInfoStore { private static final String TAG = "InferenceInfoStore"; private final TreeSet<InferenceInfo> inferenceInfos; @@ -98,4 +101,4 @@ public class InferenceInfoStore { info.startTimeMs).setEndTimeMillis(info.endTimeMs).setSuspendedTimeMillis( info.suspendedTimeMs).build(); } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java index 1450dc0803d6..6badc53ae97e 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java @@ -16,7 +16,21 @@ package com.android.server.ondeviceintelligence; -public interface OnDeviceIntelligenceManagerInternal { +import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE; + +import android.annotation.FlaggedApi; +import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; + +/** + * Exposes APIs to {@code system_server} components outside of the module boundaries. + * <p> This API should be access using {@link com.android.server.LocalManagerRegistry}. </p> + * + * @hide + */ +@SystemApi(client = Client.SYSTEM_SERVER) +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE) +public interface OnDeviceIntelligenceManagerLocal { /** * Gets the uid for the process that is currently hosting the * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} registered on diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index b0d69e67dac5..607ec1c71094 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,38 +16,42 @@ package com.android.server.ondeviceintelligence; +import static android.app.ondeviceintelligence.flags.Flags.enableOnDeviceIntelligenceModule; + +import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY; -import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; -import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BROADCAST_INTENT; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BROADCAST_INTENT; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY; import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams; -import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly; import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams; +import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly; import static com.android.server.ondeviceintelligence.BundleUtil.wrapWithValidation; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.AppGlobals; import android.app.ondeviceintelligence.DownloadCallback; import android.app.ondeviceintelligence.Feature; import android.app.ondeviceintelligence.FeatureDetails; +import android.app.ondeviceintelligence.ICancellationSignal; import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; import android.app.ondeviceintelligence.IListFeaturesCallback; import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager; import android.app.ondeviceintelligence.IProcessingSignal; +import android.app.ondeviceintelligence.IRemoteCallback; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IStreamingResponseCallback; import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.InferenceInfo; import android.app.ondeviceintelligence.OnDeviceIntelligenceException; +import android.app.ondeviceintelligence.utils.BinderUtils; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -58,16 +62,12 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.ICancellationSignal; -import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; @@ -82,17 +82,14 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AndroidFuture; -import com.android.internal.infra.ServiceConnector; -import com.android.internal.os.BackgroundThread; -import com.android.server.LocalServices; +import com.android.modules.utils.AndroidFuture; +import com.android.modules.utils.BackgroundThread; +import com.android.modules.utils.ServiceConnector; +import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback; -import java.io.FileDescriptor; import java.io.IOException; import java.util.List; import java.util.Objects; @@ -182,9 +179,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void onStart() { publishBinderService( Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(), - /* allowIsolated = */true); - LocalServices.addService(OnDeviceIntelligenceManagerInternal.class, - this::getRemoteInferenceServiceUid); + /* allowIsolated = */ true); + if (enableOnDeviceIntelligenceModule()) { + LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class, + this::getRemoteInferenceServiceUid); + } } @Override @@ -203,10 +202,10 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void onUserUnlocked(@NonNull TargetUser user) { Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle()); //connect to remote services(if available) during boot. - if(user.getUserHandle().equals(UserHandle.SYSTEM)) { + if (user.getUserHandle().equals(UserHandle.SYSTEM)) { try { - ensureRemoteInferenceServiceInitialized(); - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ false); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ false); } catch (Exception e) { Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e); } @@ -251,7 +250,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { remoteCallback.sendResult(null); return; } - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); mRemoteOnDeviceIntelligenceService.postAsync( service -> { AndroidFuture future = new AndroidFuture(); @@ -279,7 +278,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { PersistableBundle.EMPTY); return; } - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); mRemoteOnDeviceIntelligenceService.postAsync( service -> { @@ -317,7 +316,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { PersistableBundle.EMPTY); return; } - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); mRemoteOnDeviceIntelligenceService.postAsync( service -> { @@ -361,7 +360,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { PersistableBundle.EMPTY); return; } - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); mRemoteOnDeviceIntelligenceService.postAsync( service -> { @@ -404,7 +403,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "OnDeviceIntelligenceManagerService is unavailable", PersistableBundle.EMPTY); } - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); mRemoteOnDeviceIntelligenceService.postAsync( service -> { @@ -444,7 +443,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "OnDeviceIntelligenceManagerService is unavailable", PersistableBundle.EMPTY); } - ensureRemoteInferenceServiceInitialized(); + ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); result = mRemoteInferenceService.postAsync( service -> { @@ -488,7 +487,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "OnDeviceIntelligenceManagerService is unavailable", PersistableBundle.EMPTY); } - ensureRemoteInferenceServiceInitialized(); + ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); result = mRemoteInferenceService.postAsync( service -> { @@ -534,7 +533,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "OnDeviceIntelligenceManagerService is unavailable", PersistableBundle.EMPTY); } - ensureRemoteInferenceServiceInitialized(); + ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true); int callerUid = Binder.getCallingUid(); result = mRemoteInferenceService.postAsync( service -> { @@ -559,20 +558,31 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - new OnDeviceIntelligenceShellCommand(OnDeviceIntelligenceManagerService.this).exec( - this, in, out, err, args, callback, resultReceiver); + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return new com.android.server.ondeviceintelligence.OnDeviceIntelligenceShellCommand( + OnDeviceIntelligenceManagerService.this).exec( + this, + in.getFileDescriptor(), + out.getFileDescriptor(), + err.getFileDescriptor(), + args); } }; } - private void ensureRemoteIntelligenceServiceInitialized() { + private boolean ensureRemoteIntelligenceServiceInitialized(boolean throwIfServiceInvalid) { synchronized (mLock) { if (mRemoteOnDeviceIntelligenceService == null) { String serviceName = getServiceNames()[0]; - Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, false)); - mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext, + if (!BinderUtils.withCleanCallingIdentity( + () -> validateServiceElevated(serviceName, false, + throwIfServiceInvalid))) { + return false; + } + mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService( + mContext, ComponentName.unflattenFromString(serviceName), UserHandle.SYSTEM.getIdentifier()); mRemoteOnDeviceIntelligenceService.setServiceLifecycleCallbacks( @@ -591,6 +601,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { }); } } + return true; } @NonNull @@ -604,13 +615,21 @@ public class OnDeviceIntelligenceManagerService extends SystemService { AndroidFuture<Void> result = null; try { sanitizeStateParams(processingState); - ensureRemoteInferenceServiceInitialized(); - result = mRemoteInferenceService.post( - service -> service.updateProcessingState( - processingState, callback)); - result.whenCompleteAsync( - (c, e) -> BundleUtil.tryCloseResource(processingState), - resourceClosingExecutor); + if (ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ + false)) { + result = mRemoteInferenceService.post( + service -> service.updateProcessingState( + processingState, callback)); + result.whenCompleteAsync( + (c, e) -> BundleUtil.tryCloseResource(processingState), + resourceClosingExecutor); + } else { + callback.onFailure( + OnDeviceIntelligenceException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, + "Remote service cannot be initialized."); + } + } catch (RemoteException e) { + Slog.w("Failed to invoke updateProcessingState", e); } finally { if (result == null) { resourceClosingExecutor.execute( @@ -622,11 +641,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService { }; } - private void ensureRemoteInferenceServiceInitialized() { + private boolean ensureRemoteInferenceServiceInitialized(boolean throwIfServiceInvalid) { synchronized (mLock) { if (mRemoteInferenceService == null) { String serviceName = getServiceNames()[1]; - Binder.withCleanCallingIdentity(() -> validateServiceElevated(serviceName, true)); + if (!BinderUtils.withCleanCallingIdentity( + () -> validateServiceElevated(serviceName, true, throwIfServiceInvalid))) { + return false; + } mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext, ComponentName.unflattenFromString(serviceName), UserHandle.SYSTEM.getIdentifier()); @@ -636,7 +658,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void onConnected( @NonNull IOnDeviceSandboxedInferenceService service) { try { - ensureRemoteIntelligenceServiceInitialized(); + if (!ensureRemoteIntelligenceServiceInitialized( + /* throwServiceIfInvalid */ + false)) { + return; + } service.registerRemoteStorageService( getIRemoteStorageService(), new IRemoteCallback.Stub() { @Override @@ -659,20 +685,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @Override public void onDisconnected( @NonNull IOnDeviceSandboxedInferenceService service) { - ensureRemoteIntelligenceServiceInitialized(); + if (!ensureRemoteIntelligenceServiceInitialized( + /* throwServiceIfInvalid */ + false)) { + return; + } mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected); } @Override public void onBinderDied() { - ensureRemoteIntelligenceServiceInitialized(); + if (!ensureRemoteIntelligenceServiceInitialized( + /* throwServiceIfInvalid */ + false)) { + return; + } mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceDisconnected); } }); } } + return true; } private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) { @@ -743,9 +778,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService { if (mTemporaryConfigNamespace != null) { return mTemporaryConfigNamespace; } - - return mContext.getResources().getString( - R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace); + return mContext.getResources() + .getString( + mContext.getResources() + .getIdentifier( + "config_defaultOnDeviceIntelligenceDeviceConfigNamespace", + "string", + "android")); } } @@ -759,7 +798,11 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } Bundle bundle = new Bundle(); bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle); - ensureRemoteInferenceServiceInitialized(); + if (!ensureRemoteIntelligenceServiceInitialized( + /* throwServiceIfInvalid */ + false)) { + return; + } mRemoteInferenceService.run(service -> service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() { @Override @@ -782,7 +825,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void getReadOnlyFileDescriptor( String filePath, AndroidFuture<ParcelFileDescriptor> future) { - ensureRemoteIntelligenceServiceInitialized(); + if (!ensureRemoteIntelligenceServiceInitialized( + /* throwServiceIfInvalid */ + false)) { + future.completeExceptionally(new OnDeviceIntelligenceException( + OnDeviceIntelligenceException.PROCESSING_ERROR_NOT_AVAILABLE)); + return; + } AndroidFuture<ParcelFileDescriptor> pfdFuture = new AndroidFuture<>(); mRemoteOnDeviceIntelligenceService.run( service -> service.getReadOnlyFileDescriptor( @@ -805,7 +854,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { public void getReadOnlyFeatureFileDescriptorMap( Feature feature, RemoteCallback remoteCallback) { - ensureRemoteIntelligenceServiceInitialized(); + ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true); mRemoteOnDeviceIntelligenceService.run( service -> service.getReadOnlyFeatureFileDescriptorMap( feature, @@ -829,40 +878,48 @@ public class OnDeviceIntelligenceManagerService extends SystemService { }; } - private void validateServiceElevated(String serviceName, boolean checkIsolated) { + private boolean validateServiceElevated(String serviceName, boolean checkIsolated, + boolean throwIfServiceInvalid) { try { if (TextUtils.isEmpty(serviceName)) { - throw new IllegalStateException( - "Remote service is not configured to complete the request"); + if (throwIfServiceInvalid) { + throw new IllegalStateException( + "Remote service is not configured to complete the request"); + } + return false; } ComponentName serviceComponent = ComponentName.unflattenFromString( serviceName); - ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo( + ServiceInfo serviceInfo = mContext.getPackageManager().getServiceInfo( serviceComponent, PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, - UserHandle.SYSTEM.getIdentifier()); - if (serviceInfo != null) { - if (!checkIsolated) { - checkServiceRequiresPermission(serviceInfo, - Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE); - return; - } - + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + if (!checkIsolated) { checkServiceRequiresPermission(serviceInfo, - Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE); - if (!isIsolatedService(serviceInfo)) { - throw new SecurityException( - "Call required an isolated service, but the configured service: " - + serviceName + ", is not isolated"); - } - } else { + Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE); + return true; + } + + checkServiceRequiresPermission(serviceInfo, + Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE); + if (!isIsolatedService(serviceInfo)) { + throw new SecurityException( + "Call required an isolated service, but the configured service: " + + serviceName + ", is not isolated"); + } + } catch (PackageManager.NameNotFoundException e) { + if (throwIfServiceInvalid) { throw new IllegalStateException( "Remote service is not configured to complete the request."); } - } catch (RemoteException e) { - throw new IllegalStateException("Could not fetch service info for remote services", e); + return false; + } catch (SecurityException e) { + if (throwIfServiceInvalid) { + throw e; + } + return false; } + return true; } private static void checkServiceRequiresPermission(ServiceInfo serviceInfo, @@ -870,8 +927,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService { final String permission = serviceInfo.permission; if (!requiredPermission.equals(permission)) { throw new SecurityException(String.format( - "Service %s requires %s permission. Found %s permission", - serviceInfo.getComponentName(), + "%s requires %s permission. Found %s permission", + serviceInfo, requiredPermission, serviceInfo.permission)); } @@ -909,10 +966,22 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return mTemporaryServiceNames; } } - return new String[]{mContext.getResources().getString( - R.string.config_defaultOnDeviceIntelligenceService), - mContext.getResources().getString( - R.string.config_defaultOnDeviceSandboxedInferenceService)}; + return new String[]{ + mContext.getResources() + .getString( + mContext.getResources() + .getIdentifier( + "config_defaultOnDeviceIntelligenceService", + "string", + "android")), + mContext.getResources() + .getString( + mContext.getResources() + .getIdentifier( + "config_defaultOnDeviceSandboxedInferenceService", + "string", + "android")) + }; } protected String[] getBroadcastKeys() throws Resources.NotFoundException { @@ -923,7 +992,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } - return new String[]{ MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT }; + return new String[]{MODEL_LOADED_BROADCAST_INTENT, MODEL_UNLOADED_BROADCAST_INTENT}; } @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) @@ -1068,7 +1137,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { private synchronized Handler getTemporaryHandler() { if (mTemporaryHandler == null) { - mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { + mTemporaryHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { synchronized (mLock) { @@ -1090,10 +1159,13 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return mTemporaryHandler; } + // Using #getLong here as the timeout settings are only applicable to the services running in + // SYSTEM user only. + @SuppressWarnings("NonUserGetterCalled") private long getIdleTimeoutMs() { - return Settings.Secure.getLongForUser(mContext.getContentResolver(), - Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1), - mContext.getUserId()); + return Settings.Secure.getLong(mContext.getContentResolver(), + ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, + TimeUnit.HOURS.toMillis(1)); } private int getRemoteInferenceServiceUid() { diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java index d2c84fa1b18a..c641de8b47b1 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -18,12 +18,16 @@ package com.android.server.ondeviceintelligence; import android.annotation.NonNull; import android.os.Binder; -import android.os.ShellCommand; + +import com.android.modules.utils.BasicShellCommandHandler; import java.io.PrintWriter; import java.util.Objects; -final class OnDeviceIntelligenceShellCommand extends ShellCommand { +/** + * @hide + */ +final class OnDeviceIntelligenceShellCommand extends BasicShellCommandHandler { private static final String TAG = OnDeviceIntelligenceShellCommand.class.getSimpleName(); @NonNull diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java index ac9747aa83b3..0c43a309c456 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java @@ -16,6 +16,7 @@ package com.android.server.ondeviceintelligence; +import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.BIND_INCLUDE_CAPABILITIES; @@ -26,13 +27,15 @@ import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.OnDeviceIntelligenceService; -import com.android.internal.infra.ServiceConnector; +import com.android.modules.utils.ServiceConnector; import java.util.concurrent.TimeUnit; /** * Manages the connection to the remote on-device intelligence service. Also, handles unbinding * logic set by the service implementation via a Secure Settings flag. + * + * @hide */ public class RemoteOnDeviceIntelligenceService extends ServiceConnector.Impl<IOnDeviceIntelligenceService> { @@ -56,11 +59,13 @@ public class RemoteOnDeviceIntelligenceService extends return LONG_TIMEOUT; } + // Using #getLong here as the timeout settings are only applicable to the services running in + // SYSTEM user only. @Override + @SuppressWarnings("NonUserGetterCalled") protected long getAutoDisconnectTimeoutMs() { - return Settings.Secure.getLongForUser(mContext.getContentResolver(), - Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, - TimeUnit.SECONDS.toMillis(30), - mContext.getUserId()); + return Settings.Secure.getLong(mContext.getContentResolver(), + ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30)); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java index 18b13838ea7c..8c5d5a7ba736 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java @@ -16,6 +16,7 @@ package com.android.server.ondeviceintelligence; +import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS; import static android.content.Context.BIND_FOREGROUND_SERVICE; import static android.content.Context.BIND_INCLUDE_CAPABILITIES; @@ -26,7 +27,7 @@ import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; -import com.android.internal.infra.ServiceConnector; +import com.android.modules.utils.ServiceConnector; import java.util.concurrent.TimeUnit; @@ -35,6 +36,8 @@ import java.util.concurrent.TimeUnit; * Manages the connection to the remote on-device sand boxed inference service. Also, handles * unbinding * logic set by the service implementation via a SecureSettings flag. + * + * @hide */ public class RemoteOnDeviceSandboxedInferenceService extends ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> { @@ -65,12 +68,13 @@ public class RemoteOnDeviceSandboxedInferenceService extends return LONG_TIMEOUT; } - + // Using #getLong here as the timeout settings are only applicable to the services running in + // SYSTEM user only. @Override + @SuppressWarnings("NonUserGetterCalled") protected long getAutoDisconnectTimeoutMs() { - return Settings.Secure.getLongForUser(mContext.getContentResolver(), - Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, - TimeUnit.SECONDS.toMillis(30), - mContext.getUserId()); + return Settings.Secure.getLong(mContext.getContentResolver(), + ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30)); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java index 32f0698a8f9c..249bcd37db5d 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java +++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java @@ -21,7 +21,7 @@ import android.os.Handler; import android.os.PersistableBundle; import android.os.RemoteException; -import com.android.internal.infra.AndroidFuture; +import com.android.modules.utils.AndroidFuture; import java.util.concurrent.TimeoutException; @@ -32,6 +32,8 @@ import java.util.concurrent.TimeoutException; * some cases. Instead, in such cases we rely on the remote service sending progress updates and if * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the * download will not complete and enabling faster cleanup. + * + * @hide */ public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable { private final IDownloadCallback callback; diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index 50db5018d44e..50331014f926 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -45,6 +45,17 @@ ] }, { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJUpdateSelfTestCases", "options":[ { diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index c1f254ad2b34..1a043d5015b2 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -187,3 +187,13 @@ flag { description: "Enable the connection status report for a set of hearing device." bug: "357882387" } + +flag { + name: "ignore_a2dp_disconnection_for_android_auto" + namespace: "cross_device_experiences" + description: "Do not show problem connecting message when Android Auto disconnect A2DP" + bug: "381981752" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 216574a5fff9..429e4c958f05 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -631,15 +631,15 @@ public class BluetoothUtils { assistantProfile.getAllConnectedDevices().stream() .map(deviceManager::findDevice) .filter(Objects::nonNull) - .map(CachedBluetoothDevice::getGroupId) + .map(BluetoothUtils::getGroupId) .collect(Collectors.toSet()); Set<Integer> activeGroupIds = leAudioProfile.getActiveDevices().stream() .map(deviceManager::findDevice) .filter(Objects::nonNull) - .map(CachedBluetoothDevice::getGroupId) + .map(BluetoothUtils::getGroupId) .collect(Collectors.toSet()); - int groupId = cachedDevice.getGroupId(); + int groupId = getGroupId(cachedDevice); return activeGroupIds.size() == 1 && !activeGroupIds.contains(groupId) && connectedGroupIds.size() == 2 diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 4eb0567c67d9..b58983ff1ce8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -17,6 +17,7 @@ package com.android.settingslib.bluetooth; import static com.android.settingslib.flags.Flags.enableSetPreferredTransportForLeAudioDevice; +import static com.android.settingslib.flags.Flags.ignoreA2dpDisconnectionForAndroidAuto; import android.annotation.CallbackExecutor; import android.annotation.StringRes; @@ -82,6 +83,8 @@ import java.util.stream.Stream; */ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { private static final String TAG = "CachedBluetoothDevice"; + private static final ParcelUuid ANDROID_AUTO_UUID = + ParcelUuid.fromString("4de17a00-52cb-11e6-bdf4-0800200c9a66"); // See mConnectAttempted private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; @@ -260,18 +263,26 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (mHandler.hasMessages(profile.getProfileId())) { mHandler.removeMessages(profile.getProfileId()); if (profile.getConnectionPolicy(mDevice) > - BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { - /* - * If we received state DISCONNECTED and previous state was - * CONNECTING and connection policy is FORBIDDEN or UNKNOWN - * then it's not really a failure to connect. - * - * Connection profile is considered as failed when connection - * policy indicates that profile should be connected - * but it got disconnected. - */ - Log.w(TAG, "onProfileStateChanged(): Failed to connect profile"); - setProfileConnectedStatus(profile.getProfileId(), true); + BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + if (ignoreA2dpDisconnectionForAndroidAuto() + && profile instanceof A2dpProfile && isAndroidAuto()) { + Log.w(TAG, + "onProfileStateChanged(): Skip setting A2DP " + + "connection fail for Android Auto"); + } else { + /* + * If we received state DISCONNECTED and previous state was + * CONNECTING and connection policy is FORBIDDEN or UNKNOWN + * then it's not really a failure to connect. + * + * Connection profile is considered as failed when connection + * policy indicates that profile should be connected + * but it got disconnected. + */ + Log.w(TAG, + "onProfileStateChanged(): Failed to connect profile"); + setProfileConnectedStatus(profile.getProfileId(), true); + } } } break; @@ -2031,4 +2042,16 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> void setLocalBluetoothManager(LocalBluetoothManager bluetoothManager) { mBluetoothManager = bluetoothManager; } + + private boolean isAndroidAuto() { + try { + ParcelUuid[] uuids = mDevice.getUuids(); + if (ArrayUtils.contains(uuids, ANDROID_AUTO_UUID)) { + return true; + } + } catch (RuntimeException e) { + Log.w(TAG, "Fail to check isAndroidAuto for " + this); + } + return false; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java index 76aa5bf3334c..478a5d198239 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java @@ -80,8 +80,14 @@ public final class InputRouteManager { // behavior. @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType; for (AudioDeviceInfo info : addedDevices) { - if (InputMediaDevice.isSupportedInputDevice(info.getType())) { - deviceTypeToActivate = info.getType(); + @AudioDeviceType int type = info.getType(); + // Since onAudioDevicesAdded is called not only when new device is hot + // plugged, but also when the switcher dialog is opened, make sure to check + // against existing device list and only activate if the device does not + // exist previously. + if (InputMediaDevice.isSupportedInputDevice(type) + && findDeviceByType(type) == null) { + deviceTypeToActivate = type; } } @@ -140,16 +146,22 @@ public final class InputRouteManager { } // TODO(b/355684672): handle edge case where there are two devices with the same type. Only - // using a single mSelectedInputDeviceType might not be enough to recognize the correct device. - public @Nullable MediaDevice getSelectedInputDevice() { + // using a single type might not be enough to recognize the correct device. + @Nullable + private MediaDevice findDeviceByType(@AudioDeviceType int type) { for (MediaDevice device : mInputMediaDevices) { - if (((InputMediaDevice) device).getAudioDeviceInfoType() == mSelectedInputDeviceType) { + if (((InputMediaDevice) device).getAudioDeviceInfoType() == type) { return device; } } return null; } + @Nullable + public MediaDevice getSelectedInputDevice() { + return findDeviceByType(mSelectedInputDeviceType); + } + private void applyDefaultSelectedTypeToAllPresets() { mSelectedInputDeviceType = retrieveDefaultSelectedDeviceType(); AudioDeviceAttributes deviceAttributes = diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java index d808a25ebc04..9c34946f0c1b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java @@ -361,6 +361,26 @@ public class InputRouteManagerTest { } @Test + public void onAudioDevicesAdded_doNotActivatePreexistingDevice() { + final AudioManager audioManager = mock(AudioManager.class); + InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); + + final AudioDeviceInfo info = mockWiredHeadsetInfo(); + InputMediaDevice device = createInputMediaDeviceFromDeviceInfo(info); + inputRouteManager.mInputMediaDevices.add(device); + + // Trigger onAudioDevicesAdded with a device that already exists in the device list. + AudioDeviceInfo[] devices = {info}; + inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices); + + // The device should not be activated. + for (@MediaRecorder.Source int preset : PRESETS) { + verify(audioManager, never()) + .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes()); + } + } + + @Test public void onAudioDevicesRemoved_shouldApplyDefaultSelectedDeviceToAllPresets() { final AudioManager audioManager = mock(AudioManager.class); InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager); diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 1f291cdefb03..731cb7269037 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -99,6 +99,7 @@ public class SecureSettings { Settings.Secure.RTT_CALLING_MODE, Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED, + Settings.Secure.MIRROR_BUILT_IN_DISPLAY, Settings.Secure.MATCH_CONTENT_FRAME_RATE, Settings.Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, Settings.Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index abd5b9a4a4bb..039832cee6f2 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -150,6 +150,7 @@ public class SecureSettingsValidators { Secure.INCALL_POWER_BUTTON_BEHAVIOR, new DiscreteValueValidator(new String[] {"1", "2"})); VALIDATORS.put(Secure.MINIMAL_POST_PROCESSING_ALLOWED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.MIRROR_BUILT_IN_DISPLAY, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.MATCH_CONTENT_FRAME_RATE, new DiscreteValueValidator(new String[] {"0", "1", "2"})); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags index 7eff16b0def4..0367fe0dab01 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags +++ b/packages/SettingsProvider/src/com/android/providers/settings/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.providers.settings; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 9ab853ff4964..326bff448193 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -22,6 +22,7 @@ import android.annotation.UserIdInt; import android.app.backup.BackupAgentHelper; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupRestoreEventLogger; import android.app.backup.FullBackupDataOutput; import android.content.ContentResolver; import android.content.ContentValues; @@ -82,6 +83,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.HashMap; import java.util.zip.CRC32; /** @@ -194,6 +196,22 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY = "pin_enhanced_privacy"; + // Error messages for logging metrics. + private static final String ERROR_COULD_NOT_READ_FROM_CURSOR = + "could_not_read_from_cursor"; + private static final String ERROR_FAILED_TO_WRITE_ENTITY = + "failed_to_write_entity"; + private static final String ERROR_COULD_NOT_READ_ENTITY = + "could_not_read_entity"; + private static final String ERROR_SKIPPED_BY_SYSTEM = "skipped_by_system"; + private static final String ERROR_SKIPPED_BY_BLOCKLIST = + "skipped_by_dynamic_blocklist"; + private static final String ERROR_SKIPPED_PRESERVED = "skipped_preserved"; + private static final String ERROR_SKIPPED_DUE_TO_LARGE_SCREEN = + "skipped_due_to_large_screen"; + private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation"; + + // Name of the temporary file we use during full backup/restore. This is // stored in the full-backup tarfile as well, so should not be changed. private static final String STAGE_FILE = "flattened-data"; @@ -224,6 +242,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { // The font_scale default value for this device. private float mDefaultFontScale; + @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger; + @VisibleForTesting boolean areAgentMetricsEnabled = false; + @VisibleForTesting protected Map<String, Integer> numberOfSettingsPerKey; + @Override public void onCreate() { if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked"); @@ -232,6 +254,11 @@ public class SettingsBackupAgent extends BackupAgentHelper { .getStringArray(R.array.entryvalues_font_size); mSettingsHelper = new SettingsHelper(this); mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) { + mBackupRestoreEventLogger = this.getBackupRestoreEventLogger(); + numberOfSettingsPerKey = new HashMap<>(); + areAgentMetricsEnabled = true; + } super.onCreate(); } @@ -356,7 +383,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreSettings(data, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, /* movedToSystem= */ null, R.array.restore_blocked_system_settings, dynamicBlockList, - preservedSystemSettings); + preservedSystemSettings, KEY_SYSTEM); mSettingsHelper.applyAudioSettings(); break; @@ -364,13 +391,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreSettings(data, Settings.Secure.CONTENT_URI, movedToGlobal, /* movedToSecure= */ null, movedToSystem, R.array.restore_blocked_secure_settings, dynamicBlockList, - preservedSecureSettings); + preservedSecureSettings, KEY_SECURE); break; case KEY_GLOBAL : restoreSettings(data, Settings.Global.CONTENT_URI, /* movedToGlobal= */ null, movedToSecure, movedToSystem, R.array.restore_blocked_global_settings, - dynamicBlockList, preservedGlobalSettings); + dynamicBlockList, preservedGlobalSettings, KEY_GLOBAL); break; case KEY_WIFI_SUPPLICANT : @@ -489,7 +516,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreSettings(buffer, nBytes, Settings.System.CONTENT_URI, movedToGlobal, movedToSecure, /* movedToSystem= */ null, R.array.restore_blocked_system_settings, Collections.emptySet(), - Collections.emptySet()); + Collections.emptySet(), KEY_SYSTEM); // secure settings nBytes = in.readInt(); @@ -499,7 +526,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreSettings(buffer, nBytes, Settings.Secure.CONTENT_URI, movedToGlobal, /* movedToSecure= */ null, movedToSystem, R.array.restore_blocked_secure_settings, Collections.emptySet(), - Collections.emptySet()); + Collections.emptySet(), KEY_SECURE); // Global only if sufficiently new if (version >= FULL_BACKUP_ADDED_GLOBAL) { @@ -510,7 +537,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { restoreSettings(buffer, nBytes, Settings.Global.CONTENT_URI, /* movedToGlobal= */ null, movedToSecure, movedToSystem, R.array.restore_blocked_global_settings, Collections.emptySet(), - Collections.emptySet()); + Collections.emptySet(), KEY_GLOBAL); } // locale @@ -654,23 +681,41 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (oldChecksum == newChecksum) { return oldChecksum; } + writeDataForKey(key, data, output); + return newChecksum; + } + + @VisibleForTesting + void writeDataForKey(String key, byte[] data, BackupDataOutput output) { + boolean shouldLogMetrics = + areAgentMetricsEnabled && numberOfSettingsPerKey.containsKey(key); try { if (DEBUG_BACKUP) { Log.v(TAG, "Writing entity " + key + " of size " + data.length); } output.writeEntityHeader(key, data.length); output.writeEntityData(data, data.length); + if (shouldLogMetrics) { + mBackupRestoreEventLogger + .logItemsBackedUp(key, numberOfSettingsPerKey.get(key)); + } } catch (IOException ioe) { // Bail + if (shouldLogMetrics) { + mBackupRestoreEventLogger + .logItemsBackupFailed( + key, + numberOfSettingsPerKey.get(key), + ERROR_FAILED_TO_WRITE_ENTITY); + } } - return newChecksum; } private byte[] getSystemSettings() { Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null, null, null); try { - return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP); + return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP, KEY_SYSTEM); } finally { cursor.close(); } @@ -680,7 +725,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null); try { - return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP); + return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP, KEY_SECURE); } finally { cursor.close(); } @@ -690,7 +735,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null, null, null); try { - return extractRelevantValues(cursor, getGlobalSettingsToBackup()); + return extractRelevantValues(cursor, getGlobalSettingsToBackup(), KEY_GLOBAL); } finally { cursor.close(); } @@ -773,7 +818,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { return baos.toByteArray(); } - private void restoreSettings( + @VisibleForTesting + void restoreSettings( BackupDataInput data, Uri contentUri, Set<String> movedToGlobal, @@ -781,12 +827,17 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, - Set<String> settingsToPreserve) { + Set<String> settingsToPreserve, + String settingsKey) { byte[] settings = new byte[data.getDataSize()]; try { data.readEntityData(settings, 0, settings.length); } catch (IOException ioe) { Log.e(TAG, "Couldn't read entity data"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + settingsKey, /* count= */ 1, ERROR_COULD_NOT_READ_ENTITY); + } return; } restoreSettings( @@ -798,7 +849,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToSystem, blockedSettingsArrayId, dynamicBlockList, - settingsToPreserve); + settingsToPreserve, + settingsKey); } private void restoreSettings( @@ -810,7 +862,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, - Set<String> settingsToPreserve) { + Set<String> settingsToPreserve, + String settingsKey) { restoreSettings( settings, 0, @@ -821,7 +874,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { movedToSystem, blockedSettingsArrayId, dynamicBlockList, - settingsToPreserve); + settingsToPreserve, + settingsKey); } @VisibleForTesting @@ -835,12 +889,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> movedToSystem, int blockedSettingsArrayId, Set<String> dynamicBlockList, - Set<String> settingsToPreserve) { + Set<String> settingsToPreserve, + String settingsKey) { if (DEBUG) { Log.i(TAG, "restoreSettings: " + contentUri); } - SettingsBackupWhitelist whitelist = getBackupWhitelist(contentUri); + SettingsBackupAllowlist allowlist = getBackupAllowlist(contentUri); // Restore only the white list data. final ArrayMap<String, String> cachedEntries = new ArrayMap<>(); @@ -850,7 +905,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { Set<String> blockedSettings = getBlockedSettings(blockedSettingsArrayId); - for (String key : whitelist.mSettingsWhitelist) { + int restoredSettingsCount = 0; + for (String key : allowlist.mSettingsAllowlist) { boolean isBlockedBySystem = blockedSettings != null && blockedSettings.contains(key); if (isBlockedBySystem || isBlockedByDynamicList(dynamicBlockList, contentUri, key)) { Log.i( @@ -860,6 +916,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { + " removed from restore by " + (isBlockedBySystem ? "system" : "dynamic") + " block list"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + settingsKey, + /* count= */ 1, + isBlockedBySystem ? ERROR_SKIPPED_BY_SYSTEM : ERROR_SKIPPED_BY_BLOCKLIST); + } continue; } @@ -870,12 +932,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (isSettingPreserved && !Settings.Secure.NAVIGATION_MODE.equals(key)) { Log.i(TAG, "Skipping restore for setting " + key + " as it is marked as " + "preserved"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + settingsKey, /* count= */ 1, ERROR_SKIPPED_PRESERVED); + } continue; } if (LargeScreenSettings.doNotRestoreIfLargeScreenSetting(key, getBaseContext())) { Log.i(TAG, "Skipping restore for setting " + key + " as the target device " + "is a large screen (i.e tablet or foldable in unfolded state)"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + settingsKey, /* count= */ 1, ERROR_SKIPPED_DUE_TO_LARGE_SCREEN); + } continue; } @@ -912,19 +982,34 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // only restore the settings that have valid values - if (!isValidSettingValue(key, value, whitelist.mSettingsValidators)) { + if (!isValidSettingValue(key, value, allowlist.mSettingsValidators)) { Log.w(TAG, "Attempted restore of " + key + " setting, but its value didn't pass" + " validation, value: " + value); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + settingsKey, /* count= */ 1, ERROR_DID_NOT_PASS_VALIDATION); + } continue; } final Uri destination; + // If the destination changes, we need to update the key used as datatype for metrics. + String finalSettingsKey = settingsKey; if (movedToGlobal != null && movedToGlobal.contains(key)) { destination = Settings.Global.CONTENT_URI; + if (areAgentMetricsEnabled) { + finalSettingsKey = KEY_GLOBAL; + } } else if (movedToSecure != null && movedToSecure.contains(key)) { destination = Settings.Secure.CONTENT_URI; + if (areAgentMetricsEnabled) { + finalSettingsKey = KEY_SECURE; + } } else if (movedToSystem != null && movedToSystem.contains(key)) { destination = Settings.System.CONTENT_URI; + if (areAgentMetricsEnabled) { + finalSettingsKey = KEY_SYSTEM; + } } else { destination = contentUri; } @@ -942,6 +1027,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { if (isSettingPreserved) { Log.i(TAG, "Skipping restore for setting navigation_mode " + "as it is marked as preserved"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + finalSettingsKey, /* count= */ 1, ERROR_SKIPPED_PRESERVED); + } continue; } } @@ -961,12 +1050,16 @@ public class SettingsBackupAgent extends BackupAgentHelper { Log.d(TAG, "Restored font scale from: " + toRestore + " to " + value); } - + // TODO(b/379861078): Log metrics inside this method. settingsHelper.restoreValue(this, cr, contentValues, destination, key, value, mRestoredFromSdkInt); Log.d(TAG, "Restored setting: " + destination + " : " + key + "=" + value); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestored(finalSettingsKey, /* count= */ 1); + } } + } @@ -996,29 +1089,29 @@ public class SettingsBackupAgent extends BackupAgentHelper { } @VisibleForTesting - SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { + SettingsBackupAllowlist getBackupAllowlist(Uri contentUri) { // Figure out the white list and redirects to the global table. We restore anything // in either the backup allowlist or the legacy-restore allowlist for this table. - String[] whitelist; + String[] allowlist; Map<String, Validator> validators = null; if (contentUri.equals(Settings.Secure.CONTENT_URI)) { - whitelist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP, + allowlist = ArrayUtils.concat(String.class, SecureSettings.SETTINGS_TO_BACKUP, Settings.Secure.LEGACY_RESTORE_SETTINGS, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); validators = SecureSettingsValidators.VALIDATORS; } else if (contentUri.equals(Settings.System.CONTENT_URI)) { - whitelist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP, + allowlist = ArrayUtils.concat(String.class, SystemSettings.SETTINGS_TO_BACKUP, Settings.System.LEGACY_RESTORE_SETTINGS); validators = SystemSettingsValidators.VALIDATORS; } else if (contentUri.equals(Settings.Global.CONTENT_URI)) { - whitelist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(), + allowlist = ArrayUtils.concat(String.class, getGlobalSettingsToBackup(), Settings.Global.LEGACY_RESTORE_SETTINGS); validators = GlobalSettingsValidators.VALIDATORS; } else { throw new IllegalArgumentException("Unknown URI: " + contentUri); } - return new SettingsBackupWhitelist(whitelist, validators); + return new SettingsBackupAllowlist(allowlist, validators); } private String[] getGlobalSettingsToBackup() { @@ -1118,11 +1211,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { * * @param cursor A cursor with settings data. * @param settings The settings to extract. + * @param settingsKey The key of the settings to extract (eg system). * @return The byte array of extracted values. */ - private byte[] extractRelevantValues(Cursor cursor, String[] settings) { + private byte[] extractRelevantValues( + Cursor cursor, String[] settings, String settingsKey) { if (!cursor.moveToFirst()) { Log.e(TAG, "Couldn't read from the cursor"); + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger + .logItemsBackupFailed( + settingsKey, + settings.length, + ERROR_COULD_NOT_READ_FROM_CURSOR); + } return new byte[0]; } @@ -1181,6 +1283,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(settingsKey, backedUpSettingIndex); + } + // Aggregate the result. byte[] result = new byte[totalSize]; int pos = 0; @@ -1364,7 +1470,9 @@ public class SettingsBackupAgent extends BackupAgentHelper { getContentResolver() .query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) { return extractRelevantValues( - cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP); + cursor, + DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP, + KEY_DEVICE_SPECIFIC_CONFIG); } } @@ -1399,7 +1507,8 @@ public class SettingsBackupAgent extends BackupAgentHelper { null, blockedSettingsArrayId, dynamicBlocklist, - preservedSettings); + preservedSettings, + KEY_DEVICE_SPECIFIC_CONFIG); updateWindowManagerIfNeeded(originalDensity); @@ -1597,14 +1706,14 @@ public class SettingsBackupAgent extends BackupAgentHelper { * Store the allowlist of settings to be backed up and validators for them. */ @VisibleForTesting - static class SettingsBackupWhitelist { - final String[] mSettingsWhitelist; + static class SettingsBackupAllowlist { + final String[] mSettingsAllowlist; final Map<String, Validator> mSettingsValidators; - SettingsBackupWhitelist(String[] settingsWhitelist, + SettingsBackupAllowlist(String[] settingsAllowlist, Map<String, Validator> settingsValidators) { - mSettingsWhitelist = settingsWhitelist; + mSettingsAllowlist = settingsAllowlist; mSettingsValidators = settingsValidators; } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index c0e61eefb4dd..7aed61533aac 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -409,6 +409,11 @@ final class SettingsState { Slog.w(LOG_TAG, "Bulk sync request to acongid failed."); } } + + if (Flags.disableBulkCompare()) { + return; + } + // TOBO(b/312444587): remove the comparison logic after Test Mission 2. if (requests == null) { Map<String, AconfigdFlagInfo> aconfigdFlagMap = @@ -421,7 +426,7 @@ final class SettingsState { } } - // TOBO(b/312444587): remove the comparison logic after Test Mission 2. + // TODO(b/312444587): remove the comparison logic after Test Mission 2. public int compareFlagValueInNewStorage( Map<String, AconfigdFlagInfo> defaultFlagMap, Map<String, AconfigdFlagInfo> aconfigdFlagMap) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index aca26ecce29a..cfd27c69032e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -101,3 +101,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "disable_bulk_compare" + namespace: "core_experiments_team_internal" + description: "Disable bulk comparison between DeviceConfig and aconfig storage." + bug: "312444587" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index 3a39150523ac..350c149f40de 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -17,11 +17,23 @@ package com.android.providers.settings; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertArrayEquals; - +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import android.app.backup.BackupAnnotations.BackupDestination; +import android.app.backup.BackupAnnotations.OperationType; +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.app.backup.BackupRestoreEventLogger; +import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -32,7 +44,10 @@ import android.database.MatrixCursor; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.settings.validators.SettingsValidators; import android.provider.settings.validators.Validator; @@ -43,9 +58,14 @@ import androidx.test.runner.AndroidJUnit4; import com.android.window.flags.Flags; +import java.util.List; 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; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -54,12 +74,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; + /** * Tests for the SettingsHelperTest * Usage: atest SettingsProviderTest:SettingsBackupAgentTest @@ -73,6 +95,17 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>(); private static final Map<String, String> TEST_VALUES = new HashMap<>(); private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>(); + private static final String TEST_KEY = "test_key"; + private static final String TEST_VALUE = "test_value"; + private static final String ERROR_COULD_NOT_READ_ENTITY = "could_not_read_entity"; + private static final String ERROR_SKIPPED_BY_SYSTEM = "skipped_by_system"; + private static final String ERROR_SKIPPED_BY_BLOCKLIST = + "skipped_by_dynamic_blocklist"; + private static final String ERROR_SKIPPED_PRESERVED = "skipped_preserved"; + private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation"; + private static final String KEY_SYSTEM = "system"; + private static final String KEY_SECURE = "secure"; + private static final String KEY_GLOBAL = "global"; static { DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED, @@ -86,6 +119,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR); } + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock private BackupDataInput mBackupDataInput; + @Mock private BackupDataOutput mBackupDataOutput; + private TestFriendlySettingsBackupAgent mAgentUnderTest; private Context mContext; @@ -203,19 +244,32 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Test public void testOnRestore_preservedSettingsAreNotRestored() { - SettingsBackupAgent.SettingsBackupWhitelist whitelist = - new SettingsBackupAgent.SettingsBackupWhitelist( + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( new String[] { OVERRIDDEN_TEST_SETTING, PRESERVED_TEST_SETTING }, TEST_VALUES_VALIDATORS); - mAgentUnderTest.setSettingsWhitelist(whitelist); + mAgentUnderTest.setSettingsAllowlist(allowlist); mAgentUnderTest.setBlockedSettings(); TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); mAgentUnderTest.mSettingsHelper = settingsHelper; byte[] backupData = generateBackupData(TEST_VALUES); - mAgentUnderTest.restoreSettings(backupData, /* pos */ 0, backupData.length, TEST_URI, - null, null, null, /* blockedSettingsArrayId */ 0, Collections.emptySet(), - new HashSet<>(Collections.singletonList(SettingsBackupAgent.getQualifiedKeyForSetting(PRESERVED_TEST_SETTING, TEST_URI)))); + mAgentUnderTest.restoreSettings( + backupData, + /* pos */ 0, + backupData.length, + TEST_URI, + null, + null, + null, + /* blockedSettingsArrayId */ 0, + Collections.emptySet(), + new HashSet<>(Collections + .singletonList( + SettingsBackupAgent + .getQualifiedKeyForSetting( + PRESERVED_TEST_SETTING, TEST_URI))), + TEST_KEY); assertTrue(settingsHelper.mWrittenValues.containsKey(OVERRIDDEN_TEST_SETTING)); assertFalse(settingsHelper.mWrittenValues.containsKey(PRESERVED_TEST_SETTING)); @@ -262,6 +316,486 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { assertEquals("1.5", testedMethod.apply("1.8")); } + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void onCreate_metricsFlagIsDisabled_areAgentMetricsEnabledIsFalse() { + mAgentUnderTest.onCreate(); + + assertFalse(mAgentUnderTest.areAgentMetricsEnabled); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void onCreate_flagIsEnabled_areAgentMetricsEnabledIsTrue() { + mAgentUnderTest.onCreate(); + + assertTrue(mAgentUnderTest.areAgentMetricsEnabled); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_dataWriteSucceeds_logsSuccessMetrics() + throws IOException { + when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0); + when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1); + + mAgentUnderTest.writeDataForKey( + TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityHeaderFails_logsFailureMetrics() + throws IOException { + when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenThrow(new IOException()); + when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1); + + mAgentUnderTest.writeDataForKey( + TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityDataFails_logsFailureMetrics() + throws IOException { + when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0); + when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenThrow(new IOException()); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1); + + mAgentUnderTest.writeDataForKey( + TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void writeDataForKey_metricsFlagIsDisabled_doesNotLogMetrics() + throws IOException { + when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0); + when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1); + + mAgentUnderTest.writeDataForKey( + TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput); + + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyDoesNotContainKey_doesNotLogMetrics() + throws IOException { + when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0); + when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + + mAgentUnderTest.writeDataForKey( + TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput); + + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_agentMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreDisabled_agentMetricsAreNotLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNull(loggingResult); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_readEntityDataFails_failureIsLogged() + throws IOException { + when(mBackupDataInput.readEntityData(any(byte[].class), anyInt(), anyInt())) + .thenThrow(new IOException()); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + + mAgentUnderTest.restoreSettings( + mBackupDataInput, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + assertTrue(loggingResult.getErrors().containsKey(ERROR_COULD_NOT_READ_ENTITY)); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreDisabled_readEntityDataFails_failureIsNotLogged() + throws IOException { + when(mBackupDataInput.readEntityData(any(byte[].class), anyInt(), anyInt())) + .thenThrow(new IOException()); + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + + mAgentUnderTest.restoreSettings( + mBackupDataInput, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsSkippedBySystem_failureIsLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + String[] settingBlockedBySystem = new String[] {OVERRIDDEN_TEST_SETTING}; + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + settingBlockedBySystem, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(settingBlockedBySystem); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_BY_SYSTEM)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsSkippedByBlockList_failureIsLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + Set<String> dynamicBlockList = + Set.of(Uri.withAppendedPath(TEST_URI, OVERRIDDEN_TEST_SETTING).toString()); + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + dynamicBlockList, + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_BY_BLOCKLIST)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsPreserved_failureIsLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + Set<String> preservedSettings = + Set.of(Uri.withAppendedPath(TEST_URI, OVERRIDDEN_TEST_SETTING).toString()); + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList = */ Collections.emptySet(), + preservedSettings, + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + assertTrue(loggingResult.getErrors().containsKey(ERROR_SKIPPED_PRESERVED)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsNotValid_failureIsLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + /* settingsValidators= */ null); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList = */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + assertTrue(loggingResult.getErrors().containsKey(ERROR_DID_NOT_PASS_VALIDATION)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToGlobal_agentMetricsAreLoggedWithGlobalKey() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ Set.of(OVERRIDDEN_TEST_SETTING), + /* movedToSecure= */ null, + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_GLOBAL, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToSecure_agentMetricsAreLoggedWithSecureKey() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ Set.of(OVERRIDDEN_TEST_SETTING), + /* movedToSystem= */ null, + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SECURE, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSettings_agentMetricsAreEnabled_settingIsMarkedAsMovedToSystem_agentMetricsAreLoggedWithSystemKey() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SettingsBackupAgent.SettingsBackupAllowlist allowlist = + new SettingsBackupAgent.SettingsBackupAllowlist( + new String[] {OVERRIDDEN_TEST_SETTING}, + TEST_VALUES_VALIDATORS); + mAgentUnderTest.setSettingsAllowlist(allowlist); + mAgentUnderTest.setBlockedSettings(); + TestSettingsHelper settingsHelper = new TestSettingsHelper(mContext); + mAgentUnderTest.mSettingsHelper = settingsHelper; + + byte[] backupData = generateBackupData(TEST_VALUES); + mAgentUnderTest + .restoreSettings( + backupData, + /* pos= */ 0, + backupData.length, + TEST_URI, + /* movedToGlobal= */ null, + /* movedToSecure= */ null, + /* movedToSystem= */ Set.of(OVERRIDDEN_TEST_SETTING), + /* blockedSettingsArrayId= */ 0, + /* dynamicBlockList= */ Collections.emptySet(), + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SYSTEM, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); + } + private byte[] generateBackupData(Map<String, String> keyValueData) { int totalBytes = 0; for (String key : keyValueData.keySet()) { @@ -293,7 +827,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { null, R.array.restore_blocked_global_settings, /* dynamicBlockList= */ Collections.emptySet(), - /* settingsToPreserve= */ Collections.emptySet()); + /* settingsToPreserve= */ Collections.emptySet(), + TEST_KEY); } private byte[] generateUncorruptedHeader() throws IOException { @@ -329,6 +864,21 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { } } + private DataTypeResult getLoggingResultForDatatype( + String dataType, SettingsBackupAgent agent) { + if (agent.getBackupRestoreEventLogger() == null) { + return null; + } + List<DataTypeResult> loggingResults = + agent.getBackupRestoreEventLogger().getLoggingResults(); + for (DataTypeResult result : loggingResults) { + if (result.getDataType().equals(dataType)) { + return result; + } + } + return null; + } + private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { os.write(SettingsBackupAgent.toByteArray(key)); @@ -340,7 +890,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { private static class TestFriendlySettingsBackupAgent extends SettingsBackupAgent { private Boolean mForcedDeviceInfoRestoreAcceptability = null; private String[] mBlockedSettings = null; - private SettingsBackupWhitelist mSettingsWhitelist = null; + private SettingsBackupAllowlist mSettingsAllowlist = null; void setForcedDeviceInfoRestoreAcceptability(boolean value) { mForcedDeviceInfoRestoreAcceptability = value; @@ -350,8 +900,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { mBlockedSettings = blockedSettings; } - void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) { - mSettingsWhitelist = settingsWhitelist; + void setSettingsAllowlist(SettingsBackupAllowlist settingsAllowlist) { + mSettingsAllowlist = settingsAllowlist; } @Override @@ -369,12 +919,18 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { } @Override - SettingsBackupWhitelist getBackupWhitelist(Uri contentUri) { - if (mSettingsWhitelist == null) { - return super.getBackupWhitelist(contentUri); + SettingsBackupAllowlist getBackupAllowlist(Uri contentUri) { + if (mSettingsAllowlist == null) { + return super.getBackupAllowlist(contentUri); } - return mSettingsWhitelist; + return mSettingsAllowlist; + } + + void setNumberOfSettingsPerKey(String key, int numberOfSettings) { + if (numberOfSettingsPerKey != null) { + this.numberOfSettingsPerKey.put(key, numberOfSettings); + } } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 48ce49dbfb3a..276b206cd6a1 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -27,6 +27,7 @@ import android.aconfig.Aconfig.parsed_flags; import android.aconfigd.AconfigdFlagInfo; import android.os.Looper; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.Xml; @@ -1304,6 +1305,7 @@ public class SettingsStateTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE) public void testCompareFlagValueInNewStorage() { int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); Object lock = new Object(); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 3cb74eeafa96..65f487765c1e 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -740,9 +740,6 @@ <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" /> <uses-permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE" /> - <!-- Permission required to access bugreport and screenshot files created by wear. --> - <uses-permission android:name="com.google.wear.permission.ACCESS_BUG_REPORT_FILES" /> - <!-- Permission required to run GtsAssistantTestCases --> <uses-permission android:name="android.permission.MANAGE_VOICE_KEYPHRASES" /> diff --git a/packages/Shell/res/values/defaults.xml b/packages/Shell/res/values/defaults.xml new file mode 100644 index 000000000000..b693cc826be0 --- /dev/null +++ b/packages/Shell/res/values/defaults.xml @@ -0,0 +1,5 @@ +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Default for Wear bugreport warning activity--> + <!-- DO NOT TRANSLATE --> + <string name="system_ui_wear_bugreport_warning_activity" /> +</resources>
\ No newline at end of file diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 9736831a0793..0694b6123c11 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -1365,7 +1365,11 @@ public class BugreportProgressService extends Service { */ private Intent buildWearWarningIntent() { Intent intent = new Intent(); - intent.setClassName(mContext, getPackageName() + ".WearBugreportWarningActivity"); + String systemUIPackage = mContext.getResources().getString( + com.android.internal.R.string.config_systemUi); + String wearBugreportWarningActivity = getResources() + .getString(R.string.system_ui_wear_bugreport_warning_activity); + intent.setClassName(systemUIPackage, wearBugreportWarningActivity); if (mContext.getPackageManager().resolveActivity(intent, /* flags */ 0) == null) { Log.e(TAG, "Cannot find wear bugreport warning activity"); return buildWarningIntent(mContext, /* sendIntent */ null); diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 5519b5171090..11cb0703d353 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -385,6 +385,9 @@ is ready --> <uses-permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" /> + <!-- To be able to decipher default applications for certain roles in shortcut helper --> + <uses-permission android:name="android.permission.MANAGE_DEFAULT_APPLICATIONS" /> + <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c1f786826922..ee229158decc 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1230,13 +1230,6 @@ flag { } flag { - name: "communal_hub_on_mobile" - namespace: "systemui" - description: "Brings the glanceable hub experience to mobile phones" - bug: "375689917" -} - -flag { name: "glanceable_hub_v2" namespace: "systemui" description: "Gates the refreshed glanceable hub experience that also brings the glanceable hub to mobile phones" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 3eeaf41d874a..41a00f5237f7 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -873,6 +873,9 @@ constructor( ) { // Raise closing task to "above" layer so it isn't covered. t.setLayer(target.leash, aboveLayers - i) + } else if (TransitionUtil.isOpeningType(change.mode)) { + // Put into the "below" layer space. + t.setLayer(target.leash, belowLayers - i) } } else if (TransitionInfo.isIndependent(change, info)) { // Root tasks @@ -1153,7 +1156,7 @@ constructor( // If a [controller.windowAnimatorState] exists, treat this like a takeover. takeOverAnimationInternal( window, - startWindowStates = null, + startWindowState = null, startTransaction = null, callback, ) @@ -1168,22 +1171,23 @@ constructor( callback: IRemoteAnimationFinishedCallback?, ) { val window = setUpAnimation(apps, callback) ?: return - takeOverAnimationInternal(window, startWindowStates, startTransaction, callback) + val startWindowState = startWindowStates[apps!!.indexOf(window)] + takeOverAnimationInternal(window, startWindowState, startTransaction, callback) } private fun takeOverAnimationInternal( window: RemoteAnimationTarget, - startWindowStates: Array<WindowAnimationState>?, + startWindowState: WindowAnimationState?, startTransaction: SurfaceControl.Transaction?, callback: IRemoteAnimationFinishedCallback?, ) { val useSpring = - !controller.isLaunching && startWindowStates != null && startTransaction != null + !controller.isLaunching && startWindowState != null && startTransaction != null startAnimation( window, navigationBar = null, useSpring, - startWindowStates, + startWindowState, startTransaction, callback, ) @@ -1293,7 +1297,7 @@ constructor( window: RemoteAnimationTarget, navigationBar: RemoteAnimationTarget? = null, useSpring: Boolean = false, - startingWindowStates: Array<WindowAnimationState>? = null, + startingWindowState: WindowAnimationState? = null, startTransaction: SurfaceControl.Transaction? = null, iCallback: IRemoteAnimationFinishedCallback? = null, ) { @@ -1339,6 +1343,7 @@ constructor( val isExpandingFullyAbove = transitionAnimator.isExpandingFullyAbove(controller.transitionContainer, endState) + val windowState = startingWindowState ?: controller.windowAnimatorState // We animate the opening window and delegate the view expansion to [this.controller]. val delegate = this.controller @@ -1361,18 +1366,6 @@ constructor( } } - // The states are sorted matching the changes inside the transition info. - // Using this info, the RemoteAnimationTargets are created, with their - // prefixOrderIndex fields in reverse order to that of changes. To extract - // the right state, we need to invert again. - val windowState = - if (startingWindowStates != null) { - startingWindowStates[ - startingWindowStates.size - window.prefixOrderIndex] - } else { - controller.windowAnimatorState - } - // TODO(b/323863002): use the timestamp and velocity to update the initial // position. val bounds = windowState?.bounds @@ -1461,12 +1454,6 @@ constructor( delegate.onTransitionAnimationProgress(state, progress, linearProgress) } } - val windowState = - if (startingWindowStates != null) { - startingWindowStates[startingWindowStates.size - window.prefixOrderIndex] - } else { - controller.windowAnimatorState - } val velocityPxPerS = if (longLivedReturnAnimationsEnabled() && windowState?.velocityPxPerMs != null) { val xVelocityPxPerS = windowState.velocityPxPerMs.x * 1000 @@ -1485,6 +1472,7 @@ constructor( fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow, drawHole = !controller.isBelowAnimatingWindow, startVelocity = velocityPxPerS, + startFrameTime = windowState?.timestamp ?: -1, ) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt index 558c1eba2c1c..65cd3c79cd16 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt @@ -397,6 +397,10 @@ constructor( ghostedView.visibility = View.VISIBLE ghostedView.invalidate() } + + if (isEphemeral) { + onDispose() + } } companion object { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index e2bc4095e1b5..4e889e946a5f 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -27,6 +27,8 @@ import android.graphics.drawable.GradientDrawable import android.util.FloatProperty import android.util.Log import android.util.MathUtils +import android.util.TimeUtils +import android.view.Choreographer import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay @@ -366,6 +368,7 @@ class TransitionAnimator( @get:VisibleForTesting val springY: SpringAnimation, @get:VisibleForTesting val springScale: SpringAnimation, private val springState: SpringState, + private val startFrameTime: Long, private val onAnimationStart: Runnable, ) : Animation { @get:VisibleForTesting @@ -374,6 +377,42 @@ class TransitionAnimator( override fun start() { onAnimationStart.run() + + // If no start frame time is provided, we start the springs normally. + if (startFrameTime < 0) { + startSprings() + return + } + + // This function is not guaranteed to be called inside a frame. We try to access the + // frame time immediately, but if we're not inside a frame this will throw an exception. + // We must then post a callback to be run at the beginning of the next frame. + try { + initAndStartSprings(Choreographer.getInstance().frameTime) + } catch (_: IllegalStateException) { + Choreographer.getInstance().postFrameCallback { frameTimeNanos -> + initAndStartSprings(frameTimeNanos / TimeUtils.NANOS_PER_MS) + } + } + } + + private fun initAndStartSprings(frameTime: Long) { + // Initialize the spring as if it had started at the time that its start state + // was created. + springX.doAnimationFrame(startFrameTime) + springY.doAnimationFrame(startFrameTime) + springScale.doAnimationFrame(startFrameTime) + // Move the spring time forward to the current frame, so it updates its internal state + // following the initial momentum over the elapsed time. + springX.doAnimationFrame(frameTime) + springY.doAnimationFrame(frameTime) + springScale.doAnimationFrame(frameTime) + // Actually start the spring. We do this after the previous calls because the framework + // doesn't like it when you call doAnimationFrame() after start() with an earlier time. + startSprings() + } + + private fun startSprings() { springX.start() springY.start() springScale.start() @@ -471,7 +510,9 @@ class TransitionAnimator( * is true. * * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation - * using it for the initial momentum will be used instead of the default interpolators. + * using it for the initial momentum will be used instead of the default interpolators. In this + * case, [startFrameTime] (if non-negative) represents the frame time at which the springs + * should be started. */ fun startAnimation( controller: Controller, @@ -480,6 +521,7 @@ class TransitionAnimator( fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, startVelocity: PointF? = null, + startFrameTime: Long = -1, ): Animation { if (!controller.isLaunching) assertReturnAnimations() if (startVelocity != null) assertLongLivedReturnAnimations() @@ -502,6 +544,7 @@ class TransitionAnimator( fadeWindowBackgroundLayer, drawHole, startVelocity, + startFrameTime, ) .apply { start() } } @@ -515,6 +558,7 @@ class TransitionAnimator( fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, startVelocity: PointF? = null, + startFrameTime: Long = -1, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -537,6 +581,7 @@ class TransitionAnimator( startState, endState, startVelocity, + startFrameTime, windowBackgroundLayer, transitionContainer, transitionContainerOverlay, @@ -722,6 +767,7 @@ class TransitionAnimator( startState: State, endState: State, startVelocity: PointF, + startFrameTime: Long, windowBackgroundLayer: GradientDrawable, transitionContainer: View, transitionContainerOverlay: ViewGroupOverlay, @@ -912,7 +958,7 @@ class TransitionAnimator( } } - return MultiSpringAnimation(springX, springY, springScale, springState) { + return MultiSpringAnimation(springX, springY, springScale, springState, startFrameTime) { onAnimationStart( controller, isExpandingFullyAbove, diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt index 2f83d82bbec7..92b6fd44e2f2 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt @@ -25,6 +25,7 @@ import com.android.tools.lint.detector.api.JavaContext import com.android.tools.lint.detector.api.Scope import com.android.tools.lint.detector.api.Severity import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiParameter import org.jetbrains.uast.UClass import org.jetbrains.uast.getContainingUFile @@ -40,14 +41,8 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner { if (!isInRelevantShadePackage(node)) continue if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue - // Check the any context-dependent parameter to see if it has @ShadeDisplayAware - // annotation for (parameter in constructor.parameterList.parameters) { - val shouldReport = - CONTEXT_DEPENDENT_SHADE_CLASSES.contains( - parameter.type.canonicalText - ) && !parameter.hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION) - if (shouldReport) { + if (parameter.shouldReport()) { context.report( issue = ISSUE, scope = parameter.declarationScope, @@ -62,20 +57,35 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner { companion object { private const val INJECT_ANNOTATION = "javax.inject.Inject" + private const val APPLICATION_ANNOTATION = + "com.android.systemui.dagger.qualifiers.Application" + private const val GLOBAL_CONFIG_ANNOTATION = "com.android.systemui.common.ui.GlobalConfig" private const val SHADE_DISPLAY_AWARE_ANNOTATION = "com.android.systemui.shade.ShadeDisplayAware" + private const val CONTEXT = "android.content.Context" + private const val WINDOW_MANAGER = "android.view.WindowManager" + private const val LAYOUT_INFLATER = "android.view.LayoutInflater" + private const val RESOURCES = "android.content.res.Resources" + private const val CONFIG_STATE = "com.android.systemui.common.ui.ConfigurationState" + private const val CONFIG_CONTROLLER = + "com.android.systemui.statusbar.policy.ConfigurationController" + private const val CONFIG_INTERACTOR = + "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor" + private val CONTEXT_DEPENDENT_SHADE_CLASSES = setOf( - "android.content.Context", - "android.view.WindowManager", - "android.view.LayoutInflater", - "android.content.res.Resources", - "com.android.systemui.common.ui.ConfigurationState", - "com.android.systemui.statusbar.policy.ConfigurationController", - "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor", + CONTEXT, + WINDOW_MANAGER, + LAYOUT_INFLATER, + RESOURCES, + CONFIG_STATE, + CONFIG_CONTROLLER, + CONFIG_INTERACTOR, ) + private val CONFIG_CLASSES = setOf(CONFIG_STATE, CONFIG_CONTROLLER, CONFIG_INTERACTOR) + private val SHADE_WINDOW_PACKAGES = listOf( "com.android.systemui.biometrics", @@ -93,6 +103,22 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner { "com.android.systemui.qs.customize.TileAdapter", ) + private fun PsiParameter.shouldReport(): Boolean { + val className = type.canonicalText + + // check if the parameter is a context-dependent class relevant to shade + if (className !in CONTEXT_DEPENDENT_SHADE_CLASSES) return false + // check if it has @ShadeDisplayAware + if (hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)) return false + // check if its a @Application-annotated Context + if (className == CONTEXT && hasAnnotation(APPLICATION_ANNOTATION)) return false + // check if its a @GlobalConfig-annotated ConfigurationState, ConfigurationController + // or ConfigurationInteractor + if (className in CONFIG_CLASSES && hasAnnotation(GLOBAL_CONFIG_ANNOTATION)) return false + + return true + } + private fun isInRelevantShadePackage(node: UClass): Boolean { val packageName = node.getContainingUFile()?.packageName if (packageName.isNullOrBlank()) return false @@ -102,15 +128,15 @@ class ShadeDisplayAwareDetector : Detector(), SourceCodeScanner { } private fun reportMsg(className: String) = - """UI elements of the shade window - |should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only - |@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so - |might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). - |If the usage of $className is not related to display specific configuration or UI, then there is - |technically no need to use the annotation, and you can annotate the class with - |@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker") - |""" - .trimMargin() + "UI elements of the shade window should use " + + "ShadeDisplayAware-annotated $className, as the shade might move between windows, " + + "and only @ShadeDisplayAware resources are updated with the new configuration " + + "correctly. Failures to do so might result in wrong dimensions for shade window " + + "classes (e.g. using the wrong density or theme). If the usage of $className is " + + "not related to display specific configuration or UI, then there is technically " + + "no need to use the annotation, and you can annotate the class with " + + "@SuppressLint(\"ShadeDisplayAwareContextChecker\")/" + + "@Suppress(\"ShadeDisplayAwareContextChecker\")".trimMargin() @JvmField val ISSUE: Issue = diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt index 58ad363f5b57..79f190782ee8 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt @@ -63,6 +63,26 @@ class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() { ) .indented() + private val applicationStub: TestFile = + kotlin( + """ + package com.android.systemui.dagger.qualifiers + + @Retention(AnnotationRetention.RUNTIME) annotation class Application + """ + ) + .indented() + + private val globalConfigStub: TestFile = + kotlin( + """ + package com.android.systemui.common.ui + + @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig + """ + ) + .indented() + private val configStateStub: TestFile = kotlin( """ @@ -98,6 +118,8 @@ class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() { injectStub, qsContext, shadeDisplayAwareStub, + applicationStub, + globalConfigStub, configStateStub, configControllerStub, configInteractorStub, @@ -259,6 +281,60 @@ class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() { } @Test + fun injectedConstructor_inRelevantPackage_withApplicationAnnotatedContext() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import android.content.Context + import com.android.systemui.dagger.qualifiers.Application + + class ExampleClass + @Inject + constructor(@Application private val context: Context) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test + fun injectedConstructor_inRelevantPackage_withGlobalConfigAnnotatedConfigurationClass() { + lint() + .files( + TestFiles.kotlin( + """ + package com.android.systemui.shade.example + + import javax.inject.Inject + import com.android.systemui.common.ui.ConfigurationState + import com.android.systemui.common.ui.GlobalConfig + + class ExampleClass + @Inject + constructor(@GlobalConfig private val configState: ConfigurationState) + """ + .trimIndent() + ), + *androidStubs, + *otherStubs, + ) + .issues(ShadeDisplayAwareDetector.ISSUE) + .testModes(TestMode.DEFAULT) + .run() + .expectClean() + } + + @Test fun injectedConstructor_notInRelevantPackage_withRelevantParameter_withoutAnnotation() { lint() .files( @@ -363,13 +439,13 @@ class ShadeDisplayAwareDetectorTest : SystemUILintDetectorTest() { } private fun errorMsgString(lineNumber: Int, className: String) = - """ - src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of the shade window - should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only - @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so - might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). - If the usage of $className is not related to display specific configuration or UI, then there is - technically no need to use the annotation, and you can annotate the class with - @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker") - """ + "src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of " + + "the shade window should use ShadeDisplayAware-annotated $className, as the shade " + + "might move between windows, and only @ShadeDisplayAware resources are updated with " + + "the new configuration correctly. Failures to do so might result in wrong dimensions " + + "for shade window classes (e.g. using the wrong density or theme). If the usage of " + + "$className is not related to display specific configuration or UI, then there is " + + "technically no need to use the annotation, and you can annotate the class with " + + "@SuppressLint(\"ShadeDisplayAwareContextChecker\")" + + "/@Suppress(\"ShadeDisplayAwareContextChecker\")" } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt new file mode 100644 index 000000000000..9fe85b7a7070 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -0,0 +1,497 @@ +/* + * 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.compose.gesture + +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation +import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation +import androidx.compose.foundation.gestures.horizontalDrag +import androidx.compose.foundation.gestures.verticalDrag +import androidx.compose.foundation.overscroll +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.PointerEvent +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerId +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.input.pointer.util.VelocityTracker +import androidx.compose.ui.input.pointer.util.addPointerInputChange +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.DelegatingNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.PointerInputModifierNode +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.util.fastAny +import com.android.compose.modifiers.thenIf +import kotlin.math.sign +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.launch + +/** + * A draggable that plays nicely with the nested scroll mechanism. + * + * This can be used whenever you need a draggable inside a scrollable or a draggable that contains a + * scrollable. + */ +interface NestedDraggable { + /** + * Called when a drag is started in the given [position] (*before* dragging the touch slop) and + * in the direction given by [sign]. + */ + fun onDragStarted(position: Offset, sign: Float): Controller + + /** + * Whether this draggable should consume any scroll amount with the given [sign] coming from a + * nested scrollable. + * + * This is called whenever a nested scrollable does not consume some scroll amount. If this + * returns `true`, then [onDragStarted] will be called and this draggable will have priority and + * consume all future events during preScroll until the nested scroll is finished. + */ + fun shouldConsumeNestedScroll(sign: Float): Boolean + + interface Controller { + /** + * Drag by [delta] pixels. + * + * @return the consumed [delta]. Any non-consumed delta will be dispatched to the next + * nested scroll connection to be consumed by any composable above in the hierarchy. If + * the drag was performed on this draggable directly (instead of on a nested scrollable), + * any remaining delta will be used to overscroll this draggable. + */ + fun onDrag(delta: Float): Float + + /** + * Stop the current drag with the given [velocity]. + * + * @return the consumed [velocity]. Any non-consumed velocity will be dispatched to the next + * nested scroll connection to be consumed by any composable above in the hierarchy. If + * the drag was performed on this draggable directly (instead of on a nested scrollable), + * any remaining velocity will be used to animate the overscroll of this draggable. + */ + suspend fun onDragStopped(velocity: Float): Float + } +} + +/** + * A draggable that supports nested scrolling and overscroll effects. + * + * @see NestedDraggable + */ +fun Modifier.nestedDraggable( + draggable: NestedDraggable, + orientation: Orientation, + overscrollEffect: OverscrollEffect? = null, + enabled: Boolean = true, +): Modifier { + return this.thenIf(overscrollEffect != null) { Modifier.overscroll(overscrollEffect) } + .then(NestedDraggableElement(draggable, orientation, overscrollEffect, enabled)) +} + +private data class NestedDraggableElement( + private val draggable: NestedDraggable, + private val orientation: Orientation, + private val overscrollEffect: OverscrollEffect?, + private val enabled: Boolean, +) : ModifierNodeElement<NestedDraggableNode>() { + override fun create(): NestedDraggableNode { + return NestedDraggableNode(draggable, orientation, overscrollEffect, enabled) + } + + override fun update(node: NestedDraggableNode) { + node.update(draggable, orientation, overscrollEffect, enabled) + } +} + +private class NestedDraggableNode( + private var draggable: NestedDraggable, + override var orientation: Orientation, + private var overscrollEffect: OverscrollEffect?, + private var enabled: Boolean, +) : + DelegatingNode(), + PointerInputModifierNode, + NestedScrollConnection, + CompositionLocalConsumerModifierNode, + OrientationAware { + private val nestedScrollDispatcher = NestedScrollDispatcher() + private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null + set(value) { + field?.let { undelegate(it) } + field = value?.also { delegate(it) } + } + + private var detectDragsDelegate: SuspendingPointerInputModifierNode? = null + set(value) { + field?.let { undelegate(it) } + field = value?.also { delegate(it) } + } + + /** The controller created by the nested scroll logic (and *not* the drag logic). */ + private var nestedScrollController: WrappedController? = null + set(value) { + field?.ensureOnDragStoppedIsCalled() + field = value + } + + /** + * The last pointer which was the first down since the last time all pointers were up. + * + * This is use to track the started position of a drag started on a nested scrollable. + */ + private var lastFirstDown: Offset? = null + + init { + delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) + } + + override fun onDetach() { + nestedScrollController?.ensureOnDragStoppedIsCalled() + } + + fun update( + draggable: NestedDraggable, + orientation: Orientation, + overscrollEffect: OverscrollEffect?, + enabled: Boolean, + ) { + this.draggable = draggable + this.orientation = orientation + this.overscrollEffect = overscrollEffect + this.enabled = enabled + + trackDownPositionDelegate?.resetPointerInputHandler() + detectDragsDelegate?.resetPointerInputHandler() + nestedScrollController?.ensureOnDragStoppedIsCalled() + + if (!enabled && trackDownPositionDelegate != null) { + check(detectDragsDelegate != null) + trackDownPositionDelegate = null + detectDragsDelegate = null + } + } + + override fun onPointerEvent( + pointerEvent: PointerEvent, + pass: PointerEventPass, + bounds: IntSize, + ) { + if (!enabled) return + + if (trackDownPositionDelegate == null) { + check(detectDragsDelegate == null) + trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() } + detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() } + } + + checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds) + checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds) + } + + override fun onCancelPointerInput() { + trackDownPositionDelegate?.onCancelPointerInput() + detectDragsDelegate?.onCancelPointerInput() + } + + /* + * ====================================== + * ===== Pointer input (drag) logic ===== + * ====================================== + */ + + private suspend fun PointerInputScope.detectDrags() { + // Lazily create the velocity tracker when the pointer input restarts. + val velocityTracker = VelocityTracker() + + awaitEachGesture { + val down = awaitFirstDown(requireUnconsumed = false) + var overSlop = 0f + val onTouchSlopReached = { change: PointerInputChange, over: Float -> + change.consume() + overSlop = over + } + + suspend fun AwaitPointerEventScope.awaitTouchSlopOrCancellation( + pointerId: PointerId + ): PointerInputChange? { + return when (orientation) { + Orientation.Horizontal -> + awaitHorizontalTouchSlopOrCancellation(pointerId, onTouchSlopReached) + Orientation.Vertical -> + awaitVerticalTouchSlopOrCancellation(pointerId, onTouchSlopReached) + } + } + + var drag = awaitTouchSlopOrCancellation(down.id) + + // We try to pick-up the drag gesture in case the touch slop swipe was consumed by a + // nested scrollable child that disappeared. + // This was copied from http://shortn/_10L8U02IoL. + // TODO(b/380838584): Reuse detect(Horizontal|Vertical)DragGestures() instead. + while (drag == null && currentEvent.changes.fastAny { it.pressed }) { + var event: PointerEvent + do { + event = awaitPointerEvent() + } while ( + event.changes.fastAny { it.isConsumed } && event.changes.fastAny { it.pressed } + ) + + // An event was not consumed and there's still a pointer in the screen. + if (event.changes.fastAny { it.pressed }) { + // Await touch slop again, using the initial down as starting point. + // For most cases this should return immediately since we probably moved + // far enough from the initial down event. + drag = awaitTouchSlopOrCancellation(down.id) + } + } + + if (drag != null) { + velocityTracker.resetTracking() + + val sign = (drag.position - down.position).toFloat().sign + val wrappedController = + WrappedController(coroutineScope, draggable.onDragStarted(down.position, sign)) + if (overSlop != 0f) { + onDrag(wrappedController, drag, overSlop, velocityTracker) + } + + // If a drag was started, we cancel any other drag started by a nested scrollable. + // + // Note: we cancel the nested drag here *after* starting the new drag so that in the + // STL case, the cancelled drag will not change the current scene of the STL. + nestedScrollController?.ensureOnDragStoppedIsCalled() + + val isSuccessful = + try { + val onDrag = { change: PointerInputChange -> + onDrag( + wrappedController, + change, + change.positionChange().toFloat(), + velocityTracker, + ) + change.consume() + } + + when (orientation) { + Orientation.Horizontal -> horizontalDrag(drag.id, onDrag) + Orientation.Vertical -> verticalDrag(drag.id, onDrag) + } + } catch (t: Throwable) { + wrappedController.ensureOnDragStoppedIsCalled() + throw t + } + + if (isSuccessful) { + val maxVelocity = currentValueOf(LocalViewConfiguration).maximumFlingVelocity + val velocity = + velocityTracker + .calculateVelocity(Velocity(maxVelocity, maxVelocity)) + .toFloat() + onDragStopped(wrappedController, velocity) + } else { + onDragStopped(wrappedController, velocity = 0f) + } + } + } + } + + private fun onDrag( + controller: NestedDraggable.Controller, + change: PointerInputChange, + delta: Float, + velocityTracker: VelocityTracker, + ) { + velocityTracker.addPointerInputChange(change) + + scrollWithOverscroll(delta) { deltaFromOverscroll -> + scrollWithNestedScroll(deltaFromOverscroll) { deltaFromNestedScroll -> + controller.onDrag(deltaFromNestedScroll) + } + } + } + + private fun onDragStopped(controller: WrappedController, velocity: Float) { + coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { + try { + flingWithOverscroll(velocity) { velocityFromOverscroll -> + flingWithNestedScroll(velocityFromOverscroll) { velocityFromNestedScroll -> + controller.onDragStopped(velocityFromNestedScroll) + } + } + } finally { + controller.ensureOnDragStoppedIsCalled() + } + } + } + + private fun scrollWithOverscroll(delta: Float, performScroll: (Float) -> Float): Float { + val effect = overscrollEffect + return if (effect != null) { + effect + .applyToScroll(delta.toOffset(), source = NestedScrollSource.UserInput) { + performScroll(it.toFloat()).toOffset() + } + .toFloat() + } else { + performScroll(delta) + } + } + + private fun scrollWithNestedScroll(delta: Float, performScroll: (Float) -> Float): Float { + val preConsumed = + nestedScrollDispatcher + .dispatchPreScroll( + available = delta.toOffset(), + source = NestedScrollSource.UserInput, + ) + .toFloat() + val available = delta - preConsumed + val consumed = performScroll(available) + val left = available - consumed + val postConsumed = + nestedScrollDispatcher + .dispatchPostScroll( + consumed = (preConsumed + consumed).toOffset(), + available = left.toOffset(), + source = NestedScrollSource.UserInput, + ) + .toFloat() + return consumed + preConsumed + postConsumed + } + + private suspend fun flingWithOverscroll( + velocity: Float, + performFling: suspend (Float) -> Float, + ) { + val effect = overscrollEffect + if (effect != null) { + effect.applyToFling(velocity.toVelocity()) { performFling(it.toFloat()).toVelocity() } + } else { + performFling(velocity) + } + } + + private suspend fun flingWithNestedScroll( + velocity: Float, + performFling: suspend (Float) -> Float, + ): Float { + val preConsumed = nestedScrollDispatcher.dispatchPreFling(available = velocity.toVelocity()) + val available = velocity - preConsumed.toFloat() + val consumed = performFling(available) + val left = available - consumed + return nestedScrollDispatcher + .dispatchPostFling( + consumed = consumed.toVelocity() + preConsumed, + available = left.toVelocity(), + ) + .toFloat() + } + + /* + * =============================== + * ===== Nested scroll logic ===== + * =============================== + */ + + private suspend fun PointerInputScope.trackDownPosition() { + awaitEachGesture { lastFirstDown = awaitFirstDown(requireUnconsumed = false).position } + } + + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val controller = nestedScrollController ?: return Offset.Zero + val consumed = controller.onDrag(available.toFloat()) + return consumed.toOffset() + } + + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + if (source == NestedScrollSource.SideEffect) { + check(nestedScrollController == null) + return Offset.Zero + } + + val offset = available.toFloat() + if (offset == 0f) { + return Offset.Zero + } + + val sign = offset.sign + if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) { + val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" } + nestedScrollController = + WrappedController(coroutineScope, draggable.onDragStarted(startedPosition, sign)) + } + + val controller = nestedScrollController ?: return Offset.Zero + return controller.onDrag(offset).toOffset() + } + + override suspend fun onPreFling(available: Velocity): Velocity { + val controller = nestedScrollController ?: return Velocity.Zero + nestedScrollController = null + + val consumed = controller.onDragStopped(available.toFloat()) + return consumed.toVelocity() + } +} + +/** + * A controller that wraps [delegate] and can be used to ensure that [onDragStopped] is called, but + * not more than once. + */ +private class WrappedController( + private val coroutineScope: CoroutineScope, + private val delegate: NestedDraggable.Controller, +) : NestedDraggable.Controller by delegate { + private var onDragStoppedCalled = false + + override fun onDrag(delta: Float): Float { + if (onDragStoppedCalled) return 0f + return delegate.onDrag(delta) + } + + override suspend fun onDragStopped(velocity: Float): Float { + if (onDragStoppedCalled) return 0f + onDragStoppedCalled = true + return delegate.onDragStopped(velocity) + } + + fun ensureOnDragStoppedIsCalled() { + // Start with UNDISPATCHED so that onDragStopped() is always run until its first suspension + // point, even if coroutineScope is cancelled. + coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { onDragStopped(velocity = 0f) } + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt new file mode 100644 index 000000000000..6e91727e68e3 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/OrientationAware.kt @@ -0,0 +1,57 @@ +/* + * 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.compose.gesture + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Velocity + +/** + * An interface to conveniently convert a [Float] to and from an [Offset] or a [Velocity] given an + * [orientation]. + */ +interface OrientationAware { + val orientation: Orientation + + fun Float.toOffset(): Offset { + return when (orientation) { + Orientation.Horizontal -> Offset(x = this, y = 0f) + Orientation.Vertical -> Offset(x = 0f, y = this) + } + } + + fun Float.toVelocity(): Velocity { + return when (orientation) { + Orientation.Horizontal -> Velocity(x = this, y = 0f) + Orientation.Vertical -> Velocity(x = 0f, y = this) + } + } + + fun Offset.toFloat(): Float { + return when (orientation) { + Orientation.Horizontal -> this.x + Orientation.Vertical -> this.y + } + } + + fun Velocity.toFloat(): Float { + return when (orientation) { + Orientation.Horizontal -> this.x + Orientation.Vertical -> this.y + } + } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt new file mode 100644 index 000000000000..fd3902fa7dc8 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -0,0 +1,443 @@ +/* + * 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.compose.gesture + +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeDown +import androidx.compose.ui.test.swipeLeft +import androidx.compose.ui.unit.Velocity +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.awaitCancellation +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class NestedDraggableTest(override val orientation: Orientation) : OrientationAware { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun orientations() = listOf(Orientation.Horizontal, Orientation.Vertical) + } + + @get:Rule val rule = createComposeRule() + + @Test + fun simpleDrag() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + assertThat(draggable.onDragCalled).isFalse() + assertThat(draggable.onDragStoppedCalled).isFalse() + + var rootCenter = Offset.Zero + rule.onRoot().performTouchInput { + rootCenter = center + down(center) + moveBy((touchSlop + 10f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(10f) + assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter) + assertThat(draggable.onDragStartedSign).isEqualTo(1f) + assertThat(draggable.onDragStoppedCalled).isFalse() + + rule.onRoot().performTouchInput { moveBy(20f.toOffset()) } + + assertThat(draggable.onDragDelta).isEqualTo(30f) + assertThat(draggable.onDragStoppedCalled).isFalse() + + rule.onRoot().performTouchInput { + moveBy((-15f).toOffset()) + up() + } + + assertThat(draggable.onDragDelta).isEqualTo(15f) + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun nestedScrollable() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .nestedScrollable(rememberScrollState()) + ) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + assertThat(draggable.onDragCalled).isFalse() + assertThat(draggable.onDragStoppedCalled).isFalse() + + var rootCenter = Offset.Zero + rule.onRoot().performTouchInput { + rootCenter = center + down(center) + moveBy((-touchSlop - 10f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(-10f) + assertThat(draggable.onDragStartedPosition).isEqualTo(rootCenter) + assertThat(draggable.onDragStartedSign).isEqualTo(-1f) + assertThat(draggable.onDragStoppedCalled).isFalse() + + rule.onRoot().performTouchInput { moveBy((-20f).toOffset()) } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(-30f) + assertThat(draggable.onDragStoppedCalled).isFalse() + + rule.onRoot().performTouchInput { + moveBy(15f.toOffset()) + up() + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragCalled).isTrue() + assertThat(draggable.onDragDelta).isEqualTo(-15f) + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset() { + val draggable = TestDraggable() + var orientation by mutableStateOf(orientation) + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + orientation = + when (orientation) { + Orientation.Horizontal -> Orientation.Vertical + Orientation.Vertical -> Orientation.Horizontal + } + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun onDragStoppedIsCalledWhenDraggableIsUpdatedAndReset_nestedScroll() { + val draggable = TestDraggable() + var orientation by mutableStateOf(orientation) + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .nestedScrollable(rememberScrollState()) + ) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + orientation = + when (orientation) { + Orientation.Horizontal -> Orientation.Vertical + Orientation.Vertical -> Orientation.Horizontal + } + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag() { + val draggable = TestDraggable() + var composeContent by mutableStateOf(true) + val touchSlop = + rule.setContentWithTouchSlop { + if (composeContent) { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + composeContent = false + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringDrag_nestedScroll() { + val draggable = TestDraggable() + var composeContent by mutableStateOf(true) + val touchSlop = + rule.setContentWithTouchSlop { + if (composeContent) { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .nestedScrollable(rememberScrollState()) + ) + } + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + composeContent = false + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun onDragStoppedIsCalledWhenDraggableIsRemovedDuringFling() { + val draggable = TestDraggable() + var composeContent by mutableStateOf(true) + var preFlingCalled = false + rule.setContent { + if (composeContent) { + Box( + Modifier.fillMaxSize() + // This nested scroll connection indefinitely suspends on pre fling, so that + // we can emulate what happens when the draggable is removed from + // composition while the pre-fling happens and onDragStopped() was not + // called yet. + .nestedScroll( + remember { + object : NestedScrollConnection { + override suspend fun onPreFling(available: Velocity): Velocity { + preFlingCalled = true + awaitCancellation() + } + } + } + ) + .nestedDraggable(draggable, orientation) + ) + } + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + // Swipe down. + rule.onRoot().performTouchInput { + when (orientation) { + Orientation.Horizontal -> swipeLeft() + Orientation.Vertical -> swipeDown() + } + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + assertThat(preFlingCalled).isTrue() + + composeContent = false + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + @Ignore("b/303224944#comment22") + fun onDragStoppedIsCalledWhenNestedScrollableIsRemoved() { + val draggable = TestDraggable() + var composeNestedScrollable by mutableStateOf(true) + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation) + .then( + if (composeNestedScrollable) { + Modifier.nestedScrollable(rememberScrollState()) + } else { + Modifier + } + ) + ) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy((touchSlop + 1f).toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + composeNestedScrollable = false + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + @Test + fun enabled() { + val draggable = TestDraggable() + var enabled by mutableStateOf(false) + val touchSlop = + rule.setContentWithTouchSlop { + Box( + Modifier.fillMaxSize() + .nestedDraggable(draggable, orientation, enabled = enabled) + ) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + + rule.onRoot().performTouchInput { + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isFalse() + assertThat(draggable.onDragStoppedCalled).isFalse() + + enabled = true + rule.onRoot().performTouchInput { + // Release previously up finger. + up() + + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedCalled).isTrue() + assertThat(draggable.onDragStoppedCalled).isFalse() + + enabled = false + rule.waitForIdle() + assertThat(draggable.onDragStoppedCalled).isTrue() + } + + private fun ComposeContentTestRule.setContentWithTouchSlop( + content: @Composable () -> Unit + ): Float { + var touchSlop = 0f + setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + content() + } + return touchSlop + } + + private fun Modifier.nestedScrollable(scrollState: ScrollState): Modifier { + return when (orientation) { + Orientation.Vertical -> verticalScroll(scrollState) + Orientation.Horizontal -> horizontalScroll(scrollState) + } + } + + private class TestDraggable( + private val onDragStarted: (Offset, Float) -> Unit = { _, _ -> }, + private val onDrag: (Float) -> Float = { it }, + private val onDragStopped: suspend (Float) -> Float = { it }, + private val shouldConsumeNestedScroll: (Float) -> Boolean = { true }, + ) : NestedDraggable { + var onDragStartedCalled = false + var onDragCalled = false + var onDragStoppedCalled = false + + var onDragStartedPosition = Offset.Zero + var onDragStartedSign = 0f + var onDragDelta = 0f + + override fun onDragStarted(position: Offset, sign: Float): NestedDraggable.Controller { + onDragStartedCalled = true + onDragStartedPosition = position + onDragStartedSign = sign + onDragDelta = 0f + + onDragStarted.invoke(position, sign) + return object : NestedDraggable.Controller { + override fun onDrag(delta: Float): Float { + onDragCalled = true + onDragDelta += delta + return onDrag.invoke(delta) + } + + override suspend fun onDragStopped(velocity: Float): Float { + onDragStoppedCalled = true + return onDragStopped.invoke(velocity) + } + } + } + + override fun shouldConsumeNestedScroll(sign: Float): Boolean { + return shouldConsumeNestedScroll.invoke(sign) + } + } +} 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 4705d8dd86c4..beaf9631ae5a 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 @@ -44,7 +44,6 @@ import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions -import com.android.systemui.Flags.communalHubOnMobile import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys @@ -188,7 +187,7 @@ fun CommunalContainer( scene( CommunalScenes.Blank, userActions = - if (communalHubOnMobile()) emptyMap() + if (viewModel.v2FlagEnabled()) emptyMap() else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal), ) { // This scene shows nothing only allowing for transitions to the communal scene. @@ -198,7 +197,8 @@ fun CommunalContainer( scene( CommunalScenes.Communal, userActions = - if (communalHubOnMobile()) emptyMap() else mapOf(Swipe.End to CommunalScenes.Blank), + if (viewModel.v2FlagEnabled()) emptyMap() + else mapOf(Swipe.End to CommunalScenes.Blank), ) { CommunalScene( backgroundType = backgroundType, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 778d7e7f99b3..a17a1d46554f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -23,11 +23,17 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.android.compose.animation.scene.SceneScope import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection import com.android.systemui.communal.ui.compose.section.CommunalPopupSection +import com.android.systemui.communal.ui.compose.section.CommunalToDreamButtonSection import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines @@ -48,6 +54,7 @@ constructor( private val ambientStatusBarSection: AmbientStatusBarSection, private val communalPopupSection: CommunalPopupSection, private val widgetSection: CommunalAppWidgetSection, + private val communalToDreamButtonSection: CommunalToDreamButtonSection, ) { @Composable @@ -59,7 +66,7 @@ constructor( Box(modifier = Modifier.fillMaxSize()) { with(communalPopupSection) { Popup() } with(ambientStatusBarSection) { - AmbientStatusBar(modifier = Modifier.fillMaxWidth()) + AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f)) } CommunalHub( viewModel = viewModel, @@ -81,11 +88,13 @@ constructor( Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() ) } + with(communalToDreamButtonSection) { Button() } }, ) { measurables, constraints -> val communalGridMeasurable = measurables[0] val lockIconMeasurable = measurables[1] val bottomAreaMeasurable = measurables[2] + val screensaverButtonMeasurable: Measurable? = measurables.getOrNull(3) val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) @@ -100,6 +109,15 @@ constructor( val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints) + val screensaverButtonSizeInt = screensaverButtonSize.roundToPx() + val screensaverButtonPlaceable = + screensaverButtonMeasurable?.measure( + Constraints.fixed( + width = screensaverButtonSizeInt, + height = screensaverButtonSizeInt, + ) + ) + val communalGridPlaceable = communalGridMeasurable.measure( noMinConstraints.copy(maxHeight = lockIconBounds.top) @@ -108,12 +126,22 @@ constructor( layout(constraints.maxWidth, constraints.maxHeight) { communalGridPlaceable.place(x = 0, y = 0) lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top) - bottomAreaPlaceable.place( - x = 0, - y = constraints.maxHeight - bottomAreaPlaceable.height, + + val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height + bottomAreaPlaceable.place(x = 0, y = bottomAreaTop) + screensaverButtonPlaceable?.place( + x = + constraints.maxWidth - + screensaverButtonSizeInt - + Dimensions.ItemSpacing.roundToPx(), + y = lockIconBounds.top, ) } } } } + + companion object { + val screensaverButtonSize: Dp = 64.dp + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 573e5ca5e2d5..a55b6d720dd6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -187,6 +187,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.ResizeInfo import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp +import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.lifecycle.rememberViewModel @@ -217,6 +218,7 @@ fun CommunalHub( var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } + var contentOffset: Offset by remember { mutableStateOf(Offset.Zero) } val gridState = rememberLazyGridState(viewModel.savedFirstScrollIndex, viewModel.savedFirstScrollOffset) @@ -239,9 +241,7 @@ fun CommunalHub( initialValue = !viewModel.isEditMode ) - val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) - val contentOffset = beforeContentPadding(contentPadding).toOffset() - + val minContentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) ObserveScrollEffect(gridState, viewModel) val context = LocalContext.current @@ -367,7 +367,7 @@ fun CommunalHub( ) { AccessibilityContainer(viewModel) { if (!viewModel.isEditMode && isEmptyState) { - EmptyStateCta(contentPadding = contentPadding, viewModel = viewModel) + EmptyStateCta(contentPadding = minContentPadding, viewModel = viewModel) } else { val slideOffsetInPx = with(LocalDensity.current) { Dimensions.SlideOffsetY.toPx().toInt() } @@ -396,10 +396,11 @@ fun CommunalHub( CommunalHubLazyGrid( communalContent = communalContent, viewModel = viewModel, - contentPadding = contentPadding, + minContentPadding = minContentPadding, contentOffset = contentOffset, screenWidth = screenWidth, setGridCoordinates = { gridCoordinates = it }, + setContentOffset = { contentOffset = it }, updateDragPositionForRemove = { boundingBox -> val gridOffset = gridCoordinates?.positionInWindow() val removeButtonCenter = @@ -741,8 +742,9 @@ fun calculateWidgetSize( @Composable private fun HorizontalGridWrapper( - contentPadding: PaddingValues, + minContentPadding: PaddingValues, gridState: LazyGridState, + setContentOffset: (offset: Offset) -> Unit, modifier: Modifier = Modifier, content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit, ) { @@ -751,17 +753,26 @@ private fun HorizontalGridWrapper( cellAspectRatio = 1.5f, modifier = modifier, state = gridState, - minContentPadding = contentPadding, + minContentPadding = minContentPadding, minHorizontalArrangement = Dimensions.ItemSpacing, minVerticalArrangement = Dimensions.ItemSpacing, + setContentOffset = setContentOffset, content = content, ) } else { + val layoutDirection = LocalLayoutDirection.current + val density = LocalDensity.current + + val minStartPadding = minContentPadding.calculateStartPadding(layoutDirection) + val minTopPadding = minContentPadding.calculateTopPadding() + + with(density) { setContentOffset(Offset(minStartPadding.toPx(), minTopPadding.toPx())) } + LazyHorizontalGrid( modifier = modifier, state = gridState, rows = GridCells.Fixed(CommunalContentSize.FixedSize.FULL.span), - contentPadding = contentPadding, + contentPadding = minContentPadding, horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), ) { @@ -775,13 +786,14 @@ private fun HorizontalGridWrapper( private fun BoxScope.CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, viewModel: BaseCommunalViewModel, - contentPadding: PaddingValues, + minContentPadding: PaddingValues, selectedKey: State<String?>, screenWidth: Int, contentOffset: Offset, gridState: LazyGridState, contentListState: ContentListState, setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit, + setContentOffset: (offset: Offset) -> Unit, updateDragPositionForRemove: (boundingBox: IntRect) -> Boolean, widgetConfigurator: WidgetConfigurator?, interactionHandler: RemoteViews.InteractionHandler?, @@ -832,10 +844,19 @@ private fun BoxScope.CommunalHubLazyGrid( HorizontalGridWrapper( modifier = gridModifier, gridState = gridState, - contentPadding = contentPadding, + minContentPadding = minContentPadding, + setContentOffset = setContentOffset, ) { sizeInfo -> + /** Override spans based on the responsive grid size */ + val finalizedList = + if (sizeInfo != null) { + resizeOngoingItems(list, sizeInfo.gridSize.height) + } else { + list + } + itemsIndexed( - items = list, + items = finalizedList, key = { _, item -> item.key }, contentType = { _, item -> item.key }, span = { _, item -> GridItemSpan(item.getSpanOrMax(sizeInfo?.gridSize?.height)) }, @@ -883,7 +904,7 @@ private fun BoxScope.CommunalHubLazyGrid( key = item.key, currentSpan = GridItemSpan(currentItemSpan), gridState = gridState, - gridContentPadding = contentPadding, + gridContentPadding = sizeInfo?.contentPadding ?: minContentPadding, verticalArrangement = Arrangement.spacedBy( sizeInfo?.verticalArrangement ?: Dimensions.ItemSpacing @@ -1193,6 +1214,7 @@ private fun CommunalContent( is CommunalContentModel.Smartspace -> SmartspaceContent(interactionHandler, model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) is CommunalContentModel.Umo -> Umo(viewModel, sceneScope, modifier) + is CommunalContentModel.Spacer -> Box(Modifier.fillMaxSize()) } } @@ -1723,24 +1745,22 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) val screenHeight = with(density) { windowMetrics.bounds.height().toDp() } val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() } - val verticalPadding = - ((screenHeight - toolbarHeight - hubDimensions.GridHeight + hubDimensions.GridTopSpacing) / - 2) - .coerceAtLeast(Dimensions.Spacing) - return PaddingValues( - start = Dimensions.ToolbarPaddingHorizontal, - end = Dimensions.ToolbarPaddingHorizontal, - top = verticalPadding + toolbarHeight, - bottom = verticalPadding, - ) -} - -@Composable -private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx { - return with(LocalDensity.current) { - ContentPaddingInPx( - start = paddingValues.calculateStartPadding(LocalLayoutDirection.current).toPx(), - top = paddingValues.calculateTopPadding().toPx(), + return if (communalResponsiveGrid()) { + PaddingValues( + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + top = hubDimensions.GridTopSpacing, + ) + } else { + val verticalPadding = + ((screenHeight - toolbarHeight - hubDimensions.GridHeight + + hubDimensions.GridTopSpacing) / 2) + .coerceAtLeast(Dimensions.Spacing) + PaddingValues( + start = Dimensions.ToolbarPaddingHorizontal, + end = Dimensions.ToolbarPaddingHorizontal, + top = verticalPadding + toolbarHeight, + bottom = verticalPadding, ) } } @@ -1760,10 +1780,6 @@ private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? = private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? = if (index in list.indices && list[index].isWidgetContent()) list[index].key else null -data class ContentPaddingInPx(val start: Float, val top: Float) { - fun toOffset(): Offset = Offset(start, top) -} - class Dimensions(val context: Context, val config: Configuration) { val GridTopSpacing: Dp get() { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt index 3642127d0823..21b34748a364 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResponsiveLazyHorizontalGrid.kt @@ -35,7 +35,9 @@ import androidx.compose.foundation.rememberOverscrollEffect import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize @@ -54,6 +56,7 @@ fun ResponsiveLazyHorizontalGrid( cellAspectRatio: Float, modifier: Modifier = Modifier, state: LazyGridState = rememberLazyGridState(), + setContentOffset: (offset: Offset) -> Unit = {}, minContentPadding: PaddingValues = PaddingValues(0.dp), minHorizontalArrangement: Dp = 0.dp, minVerticalArrangement: Dp = 0.dp, @@ -70,6 +73,7 @@ fun ResponsiveLazyHorizontalGrid( BoxWithConstraints(modifier) { val gridSize = rememberGridSize(maxWidth = maxWidth, maxHeight = maxHeight) val layoutDirection = LocalLayoutDirection.current + val density = LocalDensity.current val minStartPadding = minContentPadding.calculateStartPadding(layoutDirection) val minEndPadding = minContentPadding.calculateEndPadding(layoutDirection) @@ -124,14 +128,19 @@ fun ResponsiveLazyHorizontalGrid( val extraWidth = maxWidth - usedWidth val extraHeight = maxHeight - usedHeight + val finalStartPadding = minStartPadding + extraWidth / 2 + val finalTopPadding = minTopPadding + extraHeight / 2 + val finalContentPadding = PaddingValues( - start = minStartPadding + extraWidth / 2, + start = finalStartPadding, end = minEndPadding + extraWidth / 2, - top = minTopPadding + extraHeight / 2, + top = finalTopPadding, bottom = minBottomPadding + extraHeight / 2, ) + with(density) { setContentOffset(Offset(finalStartPadding.toPx(), finalTopPadding.toPx())) } + LazyHorizontalGrid( rows = GridCells.Fixed(gridSize.height), modifier = Modifier.fillMaxSize(), @@ -179,12 +188,13 @@ private fun calculateClosestSize(maxWidth: Dp, maxHeight: Dp, aspectRatio: Float * @property verticalArrangement The space between rows in the grid. * @property gridSize The size of the grid, in cell units. * @property availableHeight The maximum height an item in the grid may occupy. + * @property contentPadding The padding around the content of the grid. */ data class SizeInfo( val cellSize: DpSize, val verticalArrangement: Dp, val gridSize: IntSize, - private val contentPadding: PaddingValues, + val contentPadding: PaddingValues, private val maxHeight: Dp, ) { val availableHeight: Dp diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt new file mode 100644 index 000000000000..9421596f7116 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/section/CommunalToDreamButtonSection.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.compose.section + +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.PlatformIconButton +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.ui.viewmodel.CommunalToDreamButtonViewModel +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.res.R +import javax.inject.Inject + +class CommunalToDreamButtonSection +@Inject +constructor( + private val communalSettingsInteractor: CommunalSettingsInteractor, + private val viewModelFactory: CommunalToDreamButtonViewModel.Factory, +) { + @Composable + fun Button() { + if (!communalSettingsInteractor.isV2FlagEnabled()) { + return + } + + val viewModel = + rememberViewModel("CommunalToDreamButtonSection") { viewModelFactory.create() } + val shouldShowDreamButtonOnHub by + viewModel.shouldShowDreamButtonOnHub.collectAsStateWithLifecycle(false) + + if (!shouldShowDreamButtonOnHub) { + return + } + + PlatformIconButton( + onClick = { viewModel.onShowDreamButtonTap() }, + iconResource = R.drawable.ic_screensaver_auto, + contentDescription = + stringResource(R.string.accessibility_glanceable_hub_to_dream_button), + colors = + IconButtonDefaults.filledIconButtonColors( + contentColor = MaterialTheme.colorScheme.onPrimaryContainer, + containerColor = MaterialTheme.colorScheme.primaryContainer, + ), + ) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index 4f1acdc4da60..6591a75f2407 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -95,7 +96,9 @@ private fun PeopleScreenWithConversations( recentTiles: List<PeopleTileViewModel>, onTileClicked: (PeopleTileViewModel) -> Unit, ) { - Column(Modifier.sysuiResTag("top_level_with_conversations")) { + Column( + Modifier.fillMaxSize().safeContentPadding().sysuiResTag("top_level_with_conversations") + ) { Column( Modifier.fillMaxWidth().padding(PeopleSpacePadding), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt index d483f88edfff..9f582bc4daa9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults @@ -47,7 +48,7 @@ import com.android.systemui.res.R @Composable internal fun PeopleScreenEmpty(onGotItClicked: () -> Unit) { Column( - Modifier.fillMaxSize().padding(PeopleSpacePadding), + Modifier.fillMaxSize().safeContentPadding().padding(PeopleSpacePadding), horizontalAlignment = Alignment.CenterHorizontally, ) { Text( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 2d32fd768eaa..f7ce2153b0ec 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -261,7 +261,7 @@ private fun RowScope.ForegroundServicesButton( /** A button with an icon. */ @Composable -private fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) { +fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) { Expandable( color = colorAttr(model.backgroundColor), shape = CircleShape, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index 3fce89054b20..b1a19456ab7d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -26,8 +26,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeightIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -50,6 +52,8 @@ import com.android.systemui.qs.flags.QsDetailedView import com.android.systemui.qs.panels.ui.compose.EditMode import com.android.systemui.qs.panels.ui.compose.TileDetails import com.android.systemui.qs.panels.ui.compose.TileGrid +import com.android.systemui.qs.panels.ui.compose.toolbar.Toolbar +import com.android.systemui.qs.ui.composable.QuickSettingsShade.Dimensions.GridMaxHeight import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel @@ -122,7 +126,9 @@ constructor( // A sealed interface to represent the possible states of the `ShadeBody` sealed interface ShadeBodyState { data object Editing : ShadeBodyState + data object TileDetails : ShadeBodyState + data object Default : ShadeBodyState } @@ -149,9 +155,8 @@ fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) { ShadeBodyState.Editing -> { EditMode( viewModel = viewModel.editModeViewModel, - modifier = Modifier - .fillMaxWidth() - .padding(QuickSettingsShade.Dimensions.Padding), + modifier = + Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), ) } ShadeBodyState.TileDetails -> { @@ -182,22 +187,23 @@ fun SceneScope.QuickSettingsLayout( .padding( start = QuickSettingsShade.Dimensions.Padding, end = QuickSettingsShade.Dimensions.Padding, - top = QuickSettingsShade.Dimensions.Padding, + bottom = QuickSettingsShade.Dimensions.Padding / 2, ), ) { + Toolbar(viewModel.toolbarViewModelFactory) BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, modifier = Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), ) - Box { + Box( + modifier = + Modifier.requiredHeightIn(max = GridMaxHeight) + .verticalNestedScrollToScene() + .verticalScroll(rememberScrollState()) + ) { GridAnchor() - TileGrid( - viewModel = viewModel.tileGridViewModel, - modifier = - Modifier.fillMaxWidth() - .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), - ) + TileGrid(viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth()) } } } @@ -207,6 +213,6 @@ object QuickSettingsShade { object Dimensions { val Padding = 16.dp val BrightnessSliderHeight = 64.dp - val GridMaxHeight = 800.dp + val GridMaxHeight = 420.dp } } 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 caf5e41576f3..2d589f37f3cb 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 @@ -27,8 +27,11 @@ import com.android.compose.animation.scene.content.state.TransitionState.HasOver import com.android.compose.nestedscroll.OnStopScope import com.android.compose.nestedscroll.PriorityNestedScrollConnection import com.android.compose.nestedscroll.ScrollController +import com.android.compose.ui.util.SpaceVectorConverter import kotlin.math.absoluteValue +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext internal interface DraggableHandler { /** @@ -191,9 +194,15 @@ private class DragControllerImpl( private val draggableHandler: DraggableHandlerImpl, val swipes: Swipes, var swipeAnimation: SwipeAnimation<*>, -) : DragController { +) : DragController, SpaceVectorConverter by SpaceVectorConverter(draggableHandler.orientation) { val layoutState = draggableHandler.layoutImpl.state + val overscrollableContent: OverscrollableContent = + when (draggableHandler.orientation) { + Orientation.Vertical -> draggableHandler.layoutImpl.verticalOverscrollableContent + Orientation.Horizontal -> draggableHandler.layoutImpl.horizontalOverscrollableContent + } + /** * Whether this handle is active. If this returns false, calling [onDrag] and [onStop] will do * nothing. @@ -224,36 +233,75 @@ private class DragControllerImpl( * @return the consumed delta */ override fun onDrag(delta: Float): Float { - return onDrag(delta, swipeAnimation) + val initialAnimation = swipeAnimation + if (delta == 0f || !isDrivingTransition || initialAnimation.isAnimatingOffset()) { + return 0f + } + // swipeAnimation can change during the gesture, we want to always use the initial reference + // during the whole drag gesture. + return dragWithOverscroll(delta, animation = initialAnimation) } - private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float { - if (delta == 0f || !isDrivingTransition || swipeAnimation.isAnimatingOffset()) { - return 0f + private fun <T : ContentKey> dragWithOverscroll( + delta: Float, + animation: SwipeAnimation<T>, + ): Float { + require(delta != 0f) { "delta should not be 0" } + var overscrollEffect = overscrollableContent.currentOverscrollEffect + + // If we're already overscrolling, continue with the current effect for a smooth finish. + if (overscrollEffect == null || !overscrollEffect.isInProgress) { + // Otherwise, determine the target content (toContent or fromContent) for the new + // overscroll effect based on the gesture's direction. + val content = animation.contentByDirection(delta) + overscrollEffect = overscrollableContent.applyOverscrollEffectOn(content) + } + + // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags. + if (!overscrollEffect.node.node.isAttached) { + return drag(delta, animation) } - val distance = swipeAnimation.distance() - val previousOffset = swipeAnimation.dragOffset + return overscrollEffect + .applyToScroll( + delta = delta.toOffset(), + source = NestedScrollSource.UserInput, + performScroll = { + val preScrollAvailable = it.toFloat() + drag(preScrollAvailable, animation).toOffset() + }, + ) + .toFloat() + } + + private fun <T : ContentKey> drag(delta: Float, animation: SwipeAnimation<T>): Float { + if (delta == 0f) return 0f + + val distance = animation.distance() + val previousOffset = animation.dragOffset val desiredOffset = previousOffset + delta - val desiredProgress = swipeAnimation.computeProgress(desiredOffset) + val desiredProgress = animation.computeProgress(desiredOffset) - // Note: the distance could be negative if fromContent is above or to the left of - // toContent. + // Note: the distance could be negative if fromContent is above or to the left of toContent. val newOffset = when { distance == DistanceUnspecified || - swipeAnimation.contentTransition.isWithinProgressRange(desiredProgress) -> + animation.contentTransition.isWithinProgressRange(desiredProgress) -> desiredOffset distance > 0f -> desiredOffset.fastCoerceIn(0f, distance) else -> desiredOffset.fastCoerceIn(distance, 0f) } - swipeAnimation.dragOffset = newOffset + animation.dragOffset = newOffset return newOffset - previousOffset } override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float { - return onStop(velocity, canChangeContent, swipeAnimation) + // To ensure that any ongoing animation completes gracefully and avoids an undefined state, + // we execute the actual `onStop` logic in a non-cancellable context. This prevents the + // coroutine from being cancelled prematurely, which could interrupt the animation. + // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags. + return withContext(NonCancellable) { onStop(velocity, canChangeContent, swipeAnimation) } } private suspend fun <T : ContentKey> onStop( @@ -304,7 +352,22 @@ private class DragControllerImpl( fromContent } - return swipeAnimation.animateOffset(velocity, targetContent) + val overscrollEffect = overscrollableContent.applyOverscrollEffectOn(targetContent) + + // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags. + if (!overscrollEffect.node.node.isAttached) { + return swipeAnimation.animateOffset(velocity, targetContent) + } + + overscrollEffect.applyToFling( + velocity = velocity.toVelocity(), + performFling = { + val velocityLeft = it.toFloat() + swipeAnimation.animateOffset(velocityLeft, targetContent).toVelocity() + }, + ) + + return velocity } /** 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 a14b2b3746f5..bf7e8e823658 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 @@ -36,6 +36,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import com.android.compose.animation.scene.effect.ContentOverscrollEffect /** * [SceneTransitionLayout] is a container that automatically animates its content whenever its state @@ -283,6 +284,53 @@ typealias SceneScope = ContentScope @ElementDsl interface ContentScope : BaseContentScope { /** + * The overscroll effect applied to the content in the vertical direction. This can be used to + * customize how the content behaves when the scene is over scrolled. + * + * For example, you can use it with the `Modifier.overscroll()` modifier: + * ```kotlin + * @Composable + * fun ContentScope.MyScene() { + * Box( + * modifier = Modifier + * // Apply the effect + * .overscroll(verticalOverscrollEffect) + * ) { + * // ... your content ... + * } + * } + * ``` + * + * Or you can read the `overscrollDistance` value directly, if you need some custom overscroll + * behavior: + * ```kotlin + * @Composable + * fun ContentScope.MyScene() { + * Box( + * modifier = Modifier + * .graphicsLayer { + * // Translate half of the overscroll + * translationY = verticalOverscrollEffect.overscrollDistance * 0.5f + * } + * ) { + * // ... your content ... + * } + * } + * ``` + * + * @see horizontalOverscrollEffect + */ + val verticalOverscrollEffect: ContentOverscrollEffect + + /** + * The overscroll effect applied to the content in the horizontal direction. This can be used to + * customize how the content behaves when the scene is over scrolled. + * + * @see verticalOverscrollEffect + */ + val horizontalOverscrollEffect: ContentOverscrollEffect + + /** * Animate some value at the content level. * * @param value the value of this shared value in the current content. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index bdc1461f06c9..d7bac147d8f2 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -49,8 +49,10 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.Overlay import com.android.compose.animation.scene.content.Scene import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.effect.GestureEffect import com.android.compose.ui.util.lerp import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch /** The type for the content of movable elements. */ internal typealias MovableElementContent = @Composable (@Composable () -> Unit) -> Unit @@ -134,6 +136,18 @@ internal class SceneTransitionLayoutImpl( _movableContents = it } + internal var horizontalOverscrollableContent = + OverscrollableContent( + animationScope = animationScope, + overscrollEffect = { content(it).scope.horizontalOverscrollGestureEffect }, + ) + + internal var verticalOverscrollableContent = + OverscrollableContent( + animationScope = animationScope, + overscrollEffect = { content(it).scope.verticalOverscrollGestureEffect }, + ) + /** * The different values of a shared value keyed by a a [ValueKey] and the different elements and * contents it is associated to. @@ -561,3 +575,23 @@ private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) : return layout(width, height) { placeable.place(0, 0) } } } + +internal class OverscrollableContent( + private val animationScope: CoroutineScope, + private val overscrollEffect: (ContentKey) -> GestureEffect, +) { + private var currentContent: ContentKey? = null + var currentOverscrollEffect: GestureEffect? = null + + fun applyOverscrollEffectOn(contentKey: ContentKey): GestureEffect { + if (currentContent == contentKey) return currentOverscrollEffect!! + + currentOverscrollEffect?.apply { animationScope.launch { ensureApplyToFlingIsCalled() } } + + // We are wrapping the overscroll effect. + val overscrollEffect = overscrollEffect(contentKey) + currentContent = contentKey + currentOverscrollEffect = overscrollEffect + return overscrollEffect + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index ae235e5097af..35cdf81e8c14 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -313,6 +313,17 @@ internal class SwipeAnimation<T : ContentKey>( fun isAnimatingOffset(): Boolean = offsetAnimation != null + /** Get the [ContentKey] ([fromContent] or [toContent]) associated to the current [direction] */ + fun contentByDirection(direction: Float): T { + require(direction != 0f) { "Cannot find a content in this direction: $direction" } + val isDirectionToContent = (isUpOrLeft && direction < 0) || (!isUpOrLeft && direction > 0) + return if (isDirectionToContent) { + toContent + } else { + fromContent + } + } + /** * Animate the offset to a [targetContent], using the [initialVelocity] and an optional [spec] * diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 8c4cd8c93b87..152f05eb5cc7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -17,6 +17,7 @@ package com.android.compose.animation.scene.content import android.annotation.SuppressLint +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable @@ -51,6 +52,9 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.ValueKey import com.android.compose.animation.scene.animateSharedValueAsState +import com.android.compose.animation.scene.effect.GestureEffect +import com.android.compose.animation.scene.effect.OffsetOverscrollEffect +import com.android.compose.animation.scene.effect.VisualEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions import com.android.compose.animation.scene.nestedScrollToScene @@ -109,6 +113,26 @@ internal class ContentScopeImpl( override val layoutState: SceneTransitionLayoutState = layoutImpl.state + private val _verticalOverscrollEffect = + OffsetOverscrollEffect( + orientation = Orientation.Vertical, + animationScope = layoutImpl.animationScope, + ) + + private val _horizontalOverscrollEffect = + OffsetOverscrollEffect( + orientation = Orientation.Horizontal, + animationScope = layoutImpl.animationScope, + ) + + val verticalOverscrollGestureEffect = GestureEffect(_verticalOverscrollEffect) + + val horizontalOverscrollGestureEffect = GestureEffect(_horizontalOverscrollEffect) + + override val verticalOverscrollEffect = VisualEffect(_verticalOverscrollEffect) + + override val horizontalOverscrollEffect = VisualEffect(_horizontalOverscrollEffect) + override fun Modifier.element(key: ElementKey): Modifier { return element(layoutImpl, content, key) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt new file mode 100644 index 000000000000..2233debde277 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/ContentOverscrollEffect.kt @@ -0,0 +1,173 @@ +/* + * 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.compose.animation.scene.effect + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.unit.Velocity +import com.android.compose.ui.util.SpaceVectorConverter +import kotlin.math.abs +import kotlin.math.sign +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +/** + * An [OverscrollEffect] that uses an [Animatable] to track and animate overscroll values along a + * specific [Orientation]. + */ +interface ContentOverscrollEffect : OverscrollEffect { + /** The current overscroll value. */ + val overscrollDistance: Float +} + +open class BaseContentOverscrollEffect( + orientation: Orientation, + private val animationScope: CoroutineScope, + private val animationSpec: AnimationSpec<Float>, +) : ContentOverscrollEffect, SpaceVectorConverter by SpaceVectorConverter(orientation) { + + /** The [Animatable] that holds the current overscroll value. */ + private val animatable = Animatable(initialValue = 0f, visibilityThreshold = 0.5f) + + override val overscrollDistance: Float + get() = animatable.value + + override val isInProgress: Boolean + get() = overscrollDistance != 0f + + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset { + val deltaForAxis = delta.toFloat() + + // If we're currently overscrolled, and the user scrolls in the opposite direction, we need + // to "relax" the overscroll by consuming some of the scroll delta to bring it back towards + // zero. + val currentOffset = animatable.value + val sameDirection = deltaForAxis.sign == currentOffset.sign + val consumedByPreScroll = + if (abs(currentOffset) > 0.5 && !sameDirection) { + // The user has scrolled in the opposite direction. + val prevOverscrollValue = currentOffset + val newOverscrollValue = currentOffset + deltaForAxis + if (sign(prevOverscrollValue) != sign(newOverscrollValue)) { + // Enough to completely cancel the overscroll. We snap the overscroll value + // back to zero and consume the corresponding amount of the scroll delta. + animationScope.launch { animatable.snapTo(0f) } + -prevOverscrollValue + } else { + // Not enough to cancel the overscroll. We update the overscroll value + // accordingly and consume the entire scroll delta. + animationScope.launch { animatable.snapTo(newOverscrollValue) } + deltaForAxis + } + } else { + 0f + } + .toOffset() + + // After handling any overscroll relaxation, we pass the remaining scroll delta to the + // standard scrolling logic. + val leftForScroll = delta - consumedByPreScroll + val consumedByScroll = performScroll(leftForScroll) + val overscrollDelta = leftForScroll - consumedByScroll + + // If the user is dragging (not flinging), and there's any remaining scroll delta after the + // standard scrolling logic has been applied, we add it to the overscroll. + if (abs(overscrollDelta.toFloat()) > 0.5 && source == NestedScrollSource.UserInput) { + animationScope.launch { animatable.snapTo(currentOffset + overscrollDelta.toFloat()) } + } + + return delta + } + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + // We launch a coroutine to ensure the fling animation starts after any pending [snapTo] + // animations have finished. + // This guarantees a smooth, sequential execution of animations on the overscroll value. + coroutineScope { + launch { + val consumed = performFling(velocity) + val remaining = velocity - consumed + animatable.animateTo(0f, animationSpec, remaining.toFloat()) + } + } + } +} + +/** An overscroll effect that ensures only a single fling animation is triggered. */ +internal class GestureEffect(private val delegate: ContentOverscrollEffect) : + ContentOverscrollEffect by delegate { + private var shouldFling = false + + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset { + shouldFling = true + return delegate.applyToScroll(delta, source, performScroll) + } + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + if (!shouldFling) { + performFling(velocity) + return + } + shouldFling = false + delegate.applyToFling(velocity, performFling) + } + + suspend fun ensureApplyToFlingIsCalled() { + applyToFling(Velocity.Zero) { Velocity.Zero } + } +} + +/** + * An overscroll effect that only applies visual effects and does not interfere with the actual + * scrolling or flinging behavior. + */ +internal class VisualEffect(private val delegate: ContentOverscrollEffect) : + ContentOverscrollEffect by delegate { + override fun applyToScroll( + delta: Offset, + source: NestedScrollSource, + performScroll: (Offset) -> Offset, + ): Offset { + return performScroll(delta) + } + + override suspend fun applyToFling( + velocity: Velocity, + performFling: suspend (Velocity) -> Velocity, + ) { + performFling(velocity) + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt new file mode 100644 index 000000000000..f459c46d3e6f --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffect.kt @@ -0,0 +1,100 @@ +/* + * 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.compose.animation.scene.effect + +import androidx.annotation.VisibleForTesting +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.foundation.OverscrollEffect +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.ProgressConverter +import kotlin.math.roundToInt +import kotlinx.coroutines.CoroutineScope + +/** An [OverscrollEffect] that offsets the content by the overscroll value. */ +class OffsetOverscrollEffect( + orientation: Orientation, + animationScope: CoroutineScope, + animationSpec: AnimationSpec<Float> = DefaultAnimationSpec, +) : BaseContentOverscrollEffect(orientation, animationScope, animationSpec) { + private var _node: DelegatableNode = newNode() + override val node: DelegatableNode + get() = _node + + fun newNode(): DelegatableNode { + return object : Modifier.Node(), LayoutModifierNode { + override fun onDetach() { + super.onDetach() + // TODO(b/379086317) Remove this workaround: avoid to reuse the same node. + _node = newNode() + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + val placeable = measurable.measure(constraints) + return layout(placeable.width, placeable.height) { + val offsetPx = computeOffset(density = this@measure, overscrollDistance) + placeable.placeRelativeWithLayer(position = offsetPx.toIntOffset()) + } + } + } + } + + companion object { + private val MaxDistance = 400.dp + + internal val DefaultAnimationSpec = + spring( + stiffness = Spring.StiffnessLow, + dampingRatio = Spring.DampingRatioLowBouncy, + visibilityThreshold = 0.5f, + ) + + @VisibleForTesting + internal fun computeOffset(density: Density, overscrollDistance: Float): Int { + val maxDistancePx = with(density) { MaxDistance.toPx() } + val progress = ProgressConverter.Default.convert(overscrollDistance / maxDistancePx) + return (progress * maxDistancePx).roundToInt() + } + } +} + +@Composable +fun rememberOffsetOverscrollEffect( + orientation: Orientation, + animationSpec: AnimationSpec<Float> = OffsetOverscrollEffect.DefaultAnimationSpec, +): OffsetOverscrollEffect { + val animationScope = rememberCoroutineScope() + return remember(orientation, animationScope, animationSpec) { + OffsetOverscrollEffect(orientation, animationScope, animationSpec) + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index a5be4dc195bc..98a00173f1d7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt @@ -48,8 +48,8 @@ fun LargeTopAppBarNestedScrollConnection( orientation = Orientation.Vertical, // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will // expand. Then, you can then scroll down the content. - canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> - offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight() + canStartPreScroll = { offsetAvailable, _, _ -> + offsetAvailable < 0 && height() > minHeight() }, // When swiping down, the content will scroll up until it reaches the top. Then, the // LargeTopAppBar will expand until it reaches its [maxHeight]. 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 a301856d024f..4410e157b526 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 @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size +import androidx.compose.foundation.overscroll import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.material3.Text @@ -47,6 +48,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.assertIsDisplayed @@ -60,6 +62,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize @@ -72,6 +75,7 @@ import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState +import com.android.compose.animation.scene.effect.OffsetOverscrollEffect import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.setContentAndCreateMainScope @@ -712,7 +716,7 @@ class ElementTest { } @Test - fun elementTransitionDuringOverscroll() { + fun elementTransitionDuringOverscrollWithOverscrollDSL() { val layoutWidth = 200.dp val layoutHeight = 400.dp val overscrollTranslateY = 10.dp @@ -765,6 +769,241 @@ class ElementTest { assertThat(animatedFloat).isEqualTo(100f) } + private fun expectedOffset(currentOffset: Dp, density: Density): Dp { + return with(density) { + OffsetOverscrollEffect.computeOffset(density, currentOffset.toPx()).toDp() + } + } + + @Test + fun elementTransitionDuringOverscroll() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + lateinit var density: Density + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }, + ) + } + rule.setContent { + density = LocalDensity.current + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) { + scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Spacer(Modifier.fillMaxSize()) + } + scene(SceneB) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .fillMaxSize() + .element(TestElements.Foo) + ) + } + } + } + assertThat(state.transitionState).isIdle() + + // Swipe by half of verticalSwipeDistance. + rule.onRoot().performTouchInput { + val middleTop = Offset((layoutWidth / 2).toPx(), 0f) + down(middleTop) + // Scroll 50%. + val firstScrollHeight = layoutHeight.toPx() * 0.5f + moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000) + } + + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) + val transition = assertThat(state.transitionState).isSceneTransition() + assertThat(transition).isNotNull() + assertThat(transition).hasProgress(0.5f) + + rule.onRoot().performTouchInput { + // Scroll another 100%. + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + // Scroll 150% (Scene B overscroll by 50%). + assertThat(transition).hasProgress(1f) + + rule + .onNodeWithTag(TestElements.Foo.testTag) + .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density)) + } + + @Test + fun elementTransitionOverscrollMultipleScenes() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + lateinit var density: Density + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = + transitions { + overscrollDisabled(SceneA, Orientation.Vertical) + overscrollDisabled(SceneB, Orientation.Vertical) + }, + ) + } + rule.setContent { + density = LocalDensity.current + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) { + scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .fillMaxSize() + .element(TestElements.Foo) + ) + } + scene(SceneB) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .fillMaxSize() + .element(TestElements.Bar) + ) + } + } + } + assertThat(state.transitionState).isIdle() + + // Swipe by half of verticalSwipeDistance. + rule.onRoot().performTouchInput { + val middleTop = Offset((layoutWidth / 2).toPx(), 0f) + down(middleTop) + val firstScrollHeight = layoutHeight.toPx() * 0.5f // Scroll 50% + moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000) + } + + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) + rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp) + val transition = assertThat(state.transitionState).isSceneTransition() + assertThat(transition).isNotNull() + assertThat(transition).hasProgress(0.5f) + + rule.onRoot().performTouchInput { + // Scroll another 100%. + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + // Scroll 150% (Scene B overscroll by 50%). + assertThat(transition).hasProgress(1f) + + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) + rule + .onNodeWithTag(TestElements.Bar.testTag) + .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density)) + + rule.onRoot().performTouchInput { + // Scroll another -30%. + moveBy(Offset(0f, layoutHeight.toPx() * -0.3f), delayMillis = 1_000) + } + + // Scroll 120% (Scene B overscroll by 20%). + assertThat(transition).hasProgress(1f) + + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) + rule + .onNodeWithTag(TestElements.Bar.testTag) + .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.2f, density)) + rule.onRoot().performTouchInput { + // Scroll another -70% + moveBy(Offset(0f, layoutHeight.toPx() * -0.7f), delayMillis = 1_000) + } + + // Scroll 50% (No overscroll). + assertThat(transition).hasProgress(0.5f) + + rule.onNodeWithTag(TestElements.Foo.testTag).assertTopPositionInRootIsEqualTo(0.dp) + rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + // Scroll another -100%. + moveBy(Offset(0f, layoutHeight.toPx() * -1f), delayMillis = 1_000) + } + + // Scroll -50% (Scene A overscroll by -50%). + assertThat(transition).hasProgress(0f) + rule + .onNodeWithTag(TestElements.Foo.testTag) + .assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * -0.5f, density)) + rule.onNodeWithTag(TestElements.Bar.testTag).assertTopPositionInRootIsEqualTo(0.dp) + } + + @Test + fun elementTransitionOverscroll() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + lateinit var density: Density + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = + transitions { + defaultOverscrollProgressConverter = ProgressConverter.linear() + overscrollDisabled(SceneB, Orientation.Vertical) + }, + ) + } + rule.setContent { + density = LocalDensity.current + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) { + scene(key = SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Spacer(Modifier.fillMaxSize()) + } + scene(SceneB) { + Spacer( + Modifier.overscroll(verticalOverscrollEffect) + .element(TestElements.Foo) + .fillMaxSize() + ) + } + } + } + assertThat(state.transitionState).isIdle() + + // Swipe by half of verticalSwipeDistance. + rule.onRoot().performTouchInput { + val middleTop = Offset((layoutWidth / 2).toPx(), 0f) + down(middleTop) + val firstScrollHeight = layoutHeight.toPx() * 0.5f // Scroll 50% + moveBy(Offset(0f, touchSlop + firstScrollHeight), delayMillis = 1_000) + } + + val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag) + fooElement.assertTopPositionInRootIsEqualTo(0.dp) + val transition = assertThat(state.transitionState).isSceneTransition() + assertThat(transition).isNotNull() + assertThat(transition).hasProgress(0.5f) + + rule.onRoot().performTouchInput { + // Scroll another 100%. + moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000) + } + + // Scroll 150% (Scene B overscroll by 50%). + assertThat(transition).hasProgress(1f) + + fooElement.assertTopPositionInRootIsEqualTo(expectedOffset(layoutHeight * 0.5f, density)) + } + @Test fun elementTransitionDuringNestedScrollOverscroll() { // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt new file mode 100644 index 000000000000..da8fe3094448 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt @@ -0,0 +1,175 @@ +/* + * 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.compose.animation.scene.effect + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.overscroll +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalViewConfiguration +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlin.properties.Delegates +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class OffsetOverscrollEffectTest { + @get:Rule val rule = createComposeRule() + + private val BOX_TAG = "box" + + private data class LayoutInfo(val layoutSize: Dp, val touchSlop: Float, val density: Density) { + fun expectedOffset(currentOffset: Dp): Dp { + return with(density) { + OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp() + } + } + } + + private fun setupOverscrollableBox( + scrollableOrientation: Orientation, + overscrollEffectOrientation: Orientation = scrollableOrientation, + ): LayoutInfo { + val layoutSize: Dp = 200.dp + var touchSlop: Float by Delegates.notNull() + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + lateinit var density: Density + rule.setContent { + density = LocalDensity.current + touchSlop = LocalViewConfiguration.current.touchSlop + val overscrollEffect = rememberOffsetOverscrollEffect(overscrollEffectOrientation) + + Box( + Modifier.overscroll(overscrollEffect) + // A scrollable that does not consume the scroll gesture. + .scrollable( + state = rememberScrollableState { 0f }, + orientation = scrollableOrientation, + overscrollEffect = overscrollEffect, + ) + .size(layoutSize) + .testTag(BOX_TAG) + ) + } + return LayoutInfo(layoutSize, touchSlop, density) + } + + @Test + fun applyVerticalOffset_duringVerticalOverscroll() { + val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx()), delayMillis = 1_000) + } + + rule + .onNodeWithTag(BOX_TAG) + .assertTopPositionInRootIsEqualTo(info.expectedOffset(info.layoutSize)) + } + + @Test + fun applyNoOffset_duringHorizontalOverscroll() { + val info = + setupOverscrollableBox( + scrollableOrientation = Orientation.Vertical, + overscrollEffectOrientation = Orientation.Horizontal, + ) + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(info.touchSlop + info.layoutSize.toPx(), 0f), delayMillis = 1_000) + } + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + } + + @Test + fun backToZero_afterOverscroll() { + val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + + rule.onRoot().performTouchInput { + down(center) + moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx()), delayMillis = 1_000) + } + + rule + .onNodeWithTag(BOX_TAG) + .assertTopPositionInRootIsEqualTo(info.expectedOffset(info.layoutSize)) + + rule.onRoot().performTouchInput { up() } + + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + } + + @Test + fun offsetOverscroll_followTheTouchPointer() { + val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) + + // First gesture, drag down. + rule.onRoot().performTouchInput { + down(center) + // A full screen scroll. + moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx()), delayMillis = 1_000) + } + rule + .onNodeWithTag(BOX_TAG) + .assertTopPositionInRootIsEqualTo(info.expectedOffset(info.layoutSize)) + + rule.onRoot().performTouchInput { + // Reduced by half. + moveBy(Offset(0f, -info.layoutSize.toPx() / 2), delayMillis = 1_000) + } + rule + .onNodeWithTag(BOX_TAG) + .assertTopPositionInRootIsEqualTo(info.expectedOffset(info.layoutSize / 2)) + + rule.onRoot().performTouchInput { up() } + // Animate back to 0. + rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp) + + // Second gesture, drag up. + rule.onRoot().performTouchInput { + down(center) + // A full screen scroll. + moveBy(Offset(0f, -info.touchSlop - info.layoutSize.toPx()), delayMillis = 1_000) + } + rule + .onNodeWithTag(BOX_TAG) + .assertTopPositionInRootIsEqualTo(info.expectedOffset(-info.layoutSize)) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt index e27f9b52153d..c8fb2cb8474f 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt @@ -71,41 +71,6 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) { } @Test - fun onScrollUpAfterContentScrolled_ignoreUpEvent() { - val scrollConnection = buildScrollConnection(heightRange = 0f..2f) - height = 1f - - // scroll down consumed by a child - scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f)) - - val offsetConsumed = - scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource) - - // It should ignore all onPreScroll events - assertThat(offsetConsumed).isEqualTo(Offset.Zero) - assertThat(height).isEqualTo(1f) - } - - @Test - fun onScrollUpAfterContentReturnedToZero_consumeHeight() { - val scrollConnection = buildScrollConnection(heightRange = 0f..2f) - height = 1f - - // scroll down consumed by a child - scrollConnection.scroll(available = Offset(0f, 1f), consumedByScroll = Offset(0f, 1f)) - - // scroll up consumed by a child, the child is in its original position - scrollConnection.scroll(available = Offset(0f, -1f), consumedByScroll = Offset(0f, -1f)) - - val offsetConsumed = - scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = scrollSource) - - // It should ignore all onPreScroll events - assertThat(offsetConsumed).isEqualTo(Offset(0f, -1f)) - assertThat(height).isEqualTo(0f) - } - - @Test fun onScrollUp_consumeDownToMin() { val scrollConnection = buildScrollConnection(heightRange = 0f..2f) height = 0f diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml index 00b0c44c2077..43131b103e51 100644 --- a/packages/SystemUI/lint-baseline.xml +++ b/packages/SystemUI/lint-baseline.xml @@ -33644,7 +33644,7 @@ <issue id="ShadeDisplayAwareContextChecker" - message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" errorLine1=" @Main resources: Resources," errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location @@ -33655,7 +33655,7 @@ <issue id="ShadeDisplayAwareContextChecker" - message="UI elements of the shade window
should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Context is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" errorLine1="constructor(context: Context, val shadeViewController: ShadeViewController) {" errorLine2=" ~~~~~~~~~~~~~~~~"> <location @@ -33699,7 +33699,7 @@ <issue id="ShadeDisplayAwareContextChecker" - message="UI elements of the shade window
should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only
@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
If the usage of Resources is not related to display specific configuration or UI, then there is
technically no need to use the annotation, and you can annotate the class with
@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" errorLine1=" @Main private val resources: Resources," errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> <location @@ -33763,5 +33763,201 @@ column="5"/> </issue> + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public AuthController(Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java" + line="716" + column="35"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @NonNull WindowManager windowManager," + errorLine2=" ~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java" + line="721" + column="36"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val sysuiContext: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt" + line="72" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of ConfigurationController is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val configurationController: ConfigurationController," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt" + line="74" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java" + line="46" + column="21"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main Resources resources," + errorLine2=" ~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java" + line="52" + column="29"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" public BiometricNotificationService(@NonNull Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java" + line="148" + column="58"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" c: Context," + errorLine2=" ~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt" + line="61" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt" + line="39" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt" + line="37" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt" + line="42" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @NonNull final Context context," + errorLine2=" ~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java" + line="186" + column="36"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1="class IconBuilder @Inject constructor(private val context: Context) {" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt" + line="27" + column="39"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val windowManager: WindowManager," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt" + line="40" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt" + line="53" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" @Main private val resources: Resources," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt" + line="51" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" private val context: Context," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt" + line="51" + column="5"/> + </issue> + + <issue + id="ShadeDisplayAwareContextChecker" + message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")" + errorLine1=" windowManager: WindowManager," + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + <location + file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt" + line="53" + column="5"/> </issues> diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt new file mode 100644 index 000000000000..88206850eb60 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +import android.service.dream.dreamManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runCurrent +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.statusbar.policy.batteryController +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.whenever + +@SmallTest +@EnableFlags(FLAG_GLANCEABLE_HUB_V2) +@RunWith(AndroidJUnit4::class) +class CommunalToDreamButtonViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest: CommunalToDreamButtonViewModel by lazy { + kosmos.communalToDreamButtonViewModel + } + + @Before + fun setUp() { + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + underTest.activateIn(testScope) + } + + @Test + fun shouldShowDreamButtonOnHub_trueWhenCanDream() = + with(kosmos) { + runTest { + whenever(dreamManager.canStartDreaming(any())).thenReturn(true) + whenever(batteryController.isPluggedIn()).thenReturn(true) + + val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) + assertThat(shouldShowButton).isTrue() + } + } + + @Test + fun shouldShowDreamButtonOnHub_falseWhenCannotDream() = + with(kosmos) { + runTest { + whenever(dreamManager.canStartDreaming(any())).thenReturn(false) + whenever(batteryController.isPluggedIn()).thenReturn(true) + + val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) + assertThat(shouldShowButton).isFalse() + } + } + + @Test + fun shouldShowDreamButtonOnHub_falseWhenNotPluggedIn() = + with(kosmos) { + runTest { + whenever(dreamManager.canStartDreaming(any())).thenReturn(true) + whenever(batteryController.isPluggedIn()).thenReturn(false) + + val shouldShowButton by collectLastValue(underTest.shouldShowDreamButtonOnHub) + assertThat(shouldShowButton).isFalse() + } + } + + @Test + fun onShowDreamButtonTap_startsDream() = + with(kosmos) { + runTest { + underTest.onShowDreamButtonTap() + runCurrent() + + verify(dreamManager).startDream() + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/ResizeUtilsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/ResizeUtilsTest.kt new file mode 100644 index 000000000000..b0f48038877e --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/ResizeUtilsTest.kt @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalContentSize +import com.android.systemui.communal.util.ResizeUtils.resizeOngoingItems +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ResizeUtilsTest : SysuiTestCase() { + private val mockWidget = + mock<CommunalContentModel.WidgetContent.Widget> { + on { size } doReturn CommunalContentSize.Responsive(1) + } + + @Test + fun noOngoingContent() { + val list = listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 2) + + assertThat(resized).containsExactly(mockWidget) + } + + @Test + fun singleOngoingContent_singleRowGrid() { + val list = createOngoingListWithSize(1) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 1) + + assertThat(resized.map { it.size }) + .containsExactly(CommunalContentSize.Responsive(1), mockWidget.size) + .inOrder() + } + + @Test + fun singleOngoingContent_twoRowGrid() { + val list = createOngoingListWithSize(1) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 2) + + assertThat(resized.map { it.size }) + .containsExactly(CommunalContentSize.Responsive(2), mockWidget.size) + .inOrder() + } + + @Test + fun singleOngoingContent_threeRowGrid() { + val list = createOngoingListWithSize(1) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 3) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(2), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + // A spacer should be added as the second element to avoid mixing widget content + // with ongoing content. + assertThat(resized[1]).isInstanceOf(CommunalContentModel.Spacer::class.java) + } + + @Test + fun twoOngoingContent_singleRowGrid() { + val list = createOngoingListWithSize(2) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 1) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun twoOngoingContent_twoRowGrid() { + val list = createOngoingListWithSize(2) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 2) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun twoOngoingContent_threeRowGrid() { + val list = createOngoingListWithSize(2) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 3) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(2), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun threeOngoingContent_singleRowGrid() { + val list = createOngoingListWithSize(3) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 1) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun threeOngoingContent_twoRowGrid() { + val list = createOngoingListWithSize(3) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 2) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(2), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun threeOngoingContent_threeRowGrid() { + val list = createOngoingListWithSize(3) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 3) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun fourOngoingContent_singleRowGrid() { + val list = createOngoingListWithSize(4) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 1) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun fourOngoingContent_twoRowGrid() { + val list = createOngoingListWithSize(4) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 2) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + @Test + fun fourOngoingContent_threeRowGrid() { + val list = createOngoingListWithSize(4) + listOf(mockWidget) + val resized = resizeOngoingItems(list = list, numRows = 3) + + assertThat(resized.map { it.size }) + .containsExactly( + CommunalContentSize.Responsive(2), + CommunalContentSize.Responsive(1), + CommunalContentSize.Responsive(2), + CommunalContentSize.Responsive(1), + mockWidget.size, + ) + .inOrder() + } + + private fun createOngoingListWithSize(size: Int): List<CommunalContentModel.Ongoing> { + return List(size) { CommunalContentModel.Umo(createdTimestampMillis = 100) } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt index 790df03e6401..1e937b46dbcb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt @@ -29,8 +29,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.shared.condition.Monitor +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.withArgCaptor import kotlin.test.Test import org.junit.Before @@ -48,6 +51,8 @@ import org.mockito.kotlin.whenever @TestableLooper.RunWithLooper @RunWith(AndroidJUnit4::class) class DreamOverlayRegistrantTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val context = mock<Context>() private val packageManager = mock<PackageManager>() @@ -73,6 +78,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() { monitor, packageManager, dreamManager, + kosmos.communalSettingsInteractor, logBuffer, ) @@ -117,7 +123,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() { /** Verify overlay registered when enabled in manifest. */ @Test - @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE) + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun testRegisteredWhenEnabledWithManifest() { serviceInfo.enabled = true start() @@ -127,8 +133,10 @@ class DreamOverlayRegistrantTest : SysuiTestCase() { /** Verify overlay registered for mobile hub with flag. */ @Test - @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE) + @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun testRegisteredForMobileHub() { + kosmos.setCommunalV2ConfigEnabled(true) + start() verify(dreamManager).registerDreamOverlayService(componentName) @@ -139,7 +147,7 @@ class DreamOverlayRegistrantTest : SysuiTestCase() { * enabled. */ @Test - @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE) + @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun testDisabledForMobileWithoutMobileHub() { start() @@ -154,8 +162,9 @@ class DreamOverlayRegistrantTest : SysuiTestCase() { /** Ensure service unregistered when component is disabled at runtime. */ @Test - @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE) + @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2) fun testUnregisteredWhenComponentDisabled() { + kosmos.setCommunalV2ConfigEnabled(true) start() verify(dreamManager).registerDreamOverlayService(componentName) clearInvocations(dreamManager) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt index f924ccb42cb0..b07097d61b96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt @@ -43,7 +43,7 @@ import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.Flags.FLAG_COMMUNAL_HUB -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler @@ -55,7 +55,9 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled import com.android.systemui.communal.shared.log.CommunalUiEvent import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.complication.ComplicationHostViewController @@ -262,6 +264,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { mKeyguardUpdateMonitor, mScrimManager, mCommunalInteractor, + kosmos.communalSettingsInteractor, kosmos.sceneInteractor, mSystemDialogsCloser, mUiEventLogger, @@ -1283,7 +1286,7 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { environmentComponents.verifyNoMoreInteractions() } - @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test fun testAmbientTouchHandlersRegistration_registerHideComplicationAndCommunal() { val client = client @@ -1303,9 +1306,11 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() { .containsExactly(mHideComplicationTouchHandler, mCommunalTouchHandler) } - @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @Test fun testAmbientTouchHandlersRegistration_v2_registerOnlyHideComplication() { + kosmos.setCommunalV2ConfigEnabled(true) + val client = client // Inform the overlay service of dream starting. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt index 76434ee54627..1de38ee3a04e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt @@ -16,10 +16,8 @@ package com.android.systemui.inputdevice.tutorial.ui.viewmodel -import androidx.lifecycle.Lifecycle.Event -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -55,7 +53,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.mock import org.mockito.kotlin.mock @OptIn(ExperimentalCoroutinesApi::class) @@ -71,9 +68,6 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { private var tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD private val viewModel by lazy { createViewModel(tutorialScope) } - // createUnsafe so its methods don't have to be called on Main thread - private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java)) - @get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher) private fun createViewModel( @@ -88,7 +82,6 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { mock<InputDeviceTutorialLogger>(), SavedStateHandle(mapOf(INTENT_TUTORIAL_SCOPE_KEY to scope)), ) - lifecycle.addObserver(viewModel) return viewModel } @@ -279,7 +272,7 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { collectValues(viewModel.screen) // just to initialize viewModel peripheralsState(touchpadConnected = true) - lifecycle.handleLifecycleEvent(Event.ON_START) + viewModel.onStart(TestLifecycleOwner()) assertGesturesDisabled() } @@ -291,8 +284,8 @@ class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() { collectValues(viewModel.screen) peripheralsState(touchpadConnected = true) - lifecycle.handleLifecycleEvent(Event.ON_START) - lifecycle.handleLifecycleEvent(Event.ON_STOP) + viewModel.onStart(TestLifecycleOwner()) + viewModel.onStop(TestLifecycleOwner()) assertGesturesNotDisabled() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt new file mode 100644 index 000000000000..f78c692ee4c2 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapterTest.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.app.role.RoleManager +import android.app.role.roleManager +import android.content.Context +import android.content.Intent +import android.content.mockedContext +import android.content.packageManager +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.hardware.input.AppLaunchData +import android.hardware.input.AppLaunchData.RoleData +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.createKeyTrigger +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.META_ALT_ON +import android.view.KeyEvent.META_CTRL_ON +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.app.ResolverActivity +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource +import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup +import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo +import com.android.systemui.keyboard.shortcut.inputGestureDataAdapter +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.kosmos.runTest +import com.android.systemui.res.R +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + + +@SmallTest +@RunWith(AndroidJUnit4::class) +class InputGestureDataAdapterTest : SysuiTestCase() { + + private val kosmos = testKosmos().also { kosmos -> + kosmos.userTracker = FakeUserTracker(onCreateCurrentUserContext = { kosmos.mockedContext }) + } + private val adapter = kosmos.inputGestureDataAdapter + private val roleManager = kosmos.roleManager + private val packageManager: PackageManager = kosmos.packageManager + private val mockUserContext: Context = kosmos.mockedContext + private val intent: Intent = mock() + private val fakeResolverActivityInfo = + ActivityInfo().apply { name = ResolverActivity::class.qualifiedName } + private val fakeActivityInfo: ActivityInfo = + ActivityInfo().apply { + name = FAKE_ACTIVITY_NAME + icon = 0x1 + nonLocalizedLabel = TEST_SHORTCUT_LABEL + } + private val mockSelectorIntent: Intent = mock() + + @Before + fun setup() { + whenever(mockUserContext.packageManager).thenReturn(packageManager) + whenever(mockUserContext.getSystemService(RoleManager::class.java)).thenReturn(roleManager) + whenever(roleManager.isRoleAvailable(TEST_ROLE)).thenReturn(true) + whenever(roleManager.getDefaultApplication(TEST_ROLE)).thenReturn(TEST_ROLE_PACKAGE) + whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(mock()) + whenever(packageManager.getLaunchIntentForPackage(TEST_ROLE_PACKAGE)).thenReturn(intent) + whenever(intent.selector).thenReturn(mockSelectorIntent) + whenever(mockSelectorIntent.categories).thenReturn(setOf(TEST_ACTIVITY_CATEGORY)) + } + + @Test + fun shortcutLabel_whenDefaultAppForCategoryIsNotSet_loadsLabelFromFirstAppMatchingIntent() = + kosmos.runTest { + setApiToRetrieveResolverActivity() + + val inputGestureData = buildInputGestureDataForAppLaunchShortcut() + val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData)) + val label = + internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label + + assertThat(label).isEqualTo(expectedShortcutLabelForFirstAppMatchingIntent) + } + + @Test + fun shortcutLabel_whenDefaultAppForCategoryIsSet_loadsLabelOfDefaultApp() { + kosmos.runTest { + setApiToRetrieveSpecificActivity() + + val inputGestureData = buildInputGestureDataForAppLaunchShortcut() + val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData)) + val label = + internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.label + + assertThat(label).isEqualTo(TEST_SHORTCUT_LABEL) + } + } + + @Test + fun shortcutIcon_whenDefaultAppForCategoryIsSet_loadsIconOfDefaultApp() { + kosmos.runTest { + setApiToRetrieveSpecificActivity() + + val inputGestureData = buildInputGestureDataForAppLaunchShortcut() + val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData)) + val icon = + internalGroups.firstOrNull()?.groups?.firstOrNull()?.items?.firstOrNull()?.icon + + assertThat(icon).isNotNull() + } + } + + @Test + fun internalGroupSource_isCorrectlyConvertedWithSimpleInputGestureData() = + kosmos.runTest { + setApiToRetrieveResolverActivity() + + val inputGestureData = buildInputGestureDataForAppLaunchShortcut() + val internalGroups = adapter.toInternalGroupSources(listOf(inputGestureData)) + + assertThat(internalGroups).containsExactly( + InternalGroupsSource( + type = ShortcutCategoryType.AppCategories, + groups = listOf( + InternalKeyboardShortcutGroup( + label = APPLICATION_SHORTCUT_GROUP_LABEL, + items = listOf( + InternalKeyboardShortcutInfo( + label = expectedShortcutLabelForFirstAppMatchingIntent, + keycode = KEYCODE_A, + modifiers = META_CTRL_ON or META_ALT_ON, + isCustomShortcut = true + ) + ) + ) + ) + ) + ) + } + + private fun setApiToRetrieveResolverActivity() { + whenever(intent.resolveActivityInfo(eq(packageManager), anyInt())) + .thenReturn(fakeResolverActivityInfo) + } + + private fun setApiToRetrieveSpecificActivity() { + whenever(intent.resolveActivityInfo(eq(packageManager), anyInt())) + .thenReturn(fakeActivityInfo) + } + + + private fun buildInputGestureDataForAppLaunchShortcut( + keyCode: Int = KEYCODE_A, + modifiers: Int = META_CTRL_ON or META_ALT_ON, + appLaunchData: AppLaunchData = RoleData(TEST_ROLE) + ): InputGestureData { + return InputGestureData.Builder() + .setTrigger(createKeyTrigger(keyCode, modifiers)) + .setAppLaunchData(appLaunchData) + .build() + } + + private val expectedShortcutLabelForFirstAppMatchingIntent = + context.getString(R.string.keyboard_shortcut_group_applications_browser) + + private companion object { + private const val TEST_ROLE = "Test Browser Role" + private const val TEST_ROLE_PACKAGE = "test.browser.package" + private const val APPLICATION_SHORTCUT_GROUP_LABEL = "Applications" + private const val FAKE_ACTIVITY_NAME = "Fake activity" + private const val TEST_SHORTCUT_LABEL = "Test shortcut label" + private const val TEST_ACTIVITY_CATEGORY = Intent.CATEGORY_APP_BROWSER + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt index 75190e973c1b..92c76ff68b60 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt @@ -567,13 +567,6 @@ object TestShortcuts { ), simpleShortcutCategory(System, "System controls", "Show shortcuts"), simpleShortcutCategory(System, "System controls", "View recent apps"), - simpleShortcutCategory(AppCategories, "Applications", "Calculator"), - simpleShortcutCategory(AppCategories, "Applications", "Calendar"), - simpleShortcutCategory(AppCategories, "Applications", "Browser"), - simpleShortcutCategory(AppCategories, "Applications", "Contacts"), - simpleShortcutCategory(AppCategories, "Applications", "Email"), - simpleShortcutCategory(AppCategories, "Applications", "Maps"), - simpleShortcutCategory(AppCategories, "Applications", "SMS"), ) val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME) @@ -615,27 +608,6 @@ object TestShortcuts { ), simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS), simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS - ), - simpleInputGestureData( - keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING - ), - simpleInputGestureData( keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER ), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt index 77c615cce287..789b10b7830c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt @@ -25,7 +25,8 @@ import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.communal.data.repository.communalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor -import com.android.systemui.communal.domain.interactor.setCommunalEnabled +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor +import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.andSceneContainer @@ -38,7 +39,6 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -48,7 +48,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON) +@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, Flags.FLAG_GLANCEABLE_HUB_V2) @RunWith(ParameterizedAndroidJunit4::class) class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = testKosmos() @@ -69,6 +69,7 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy context = context, communalInteractor = kosmos.communalInteractor, communalSceneRepository = kosmos.communalSceneRepository, + communalSettingsInteractor = kosmos.communalSettingsInteractor, sceneInteractor = kosmos.sceneInteractor, ) } @@ -76,28 +77,30 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy @Test fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() = testScope.runTest { - kosmos.setCommunalEnabled(true) + kosmos.setCommunalV2Enabled(true) runCurrent() val lockScreenState by collectLastValue(underTest.lockScreenState) - assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + assertThat(lockScreenState) + .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java) } @Test fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() = testScope.runTest { - kosmos.setCommunalEnabled(false) + kosmos.setCommunalV2Enabled(false) val lockScreenState by collectLastValue(underTest.lockScreenState) runCurrent() - assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + assertThat(lockScreenState) + .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) } @Test fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() = testScope.runTest { - kosmos.setCommunalEnabled(true) + kosmos.setCommunalV2Enabled(true) runCurrent() assertThat(underTest.getPickerScreenState()) @@ -107,7 +110,7 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy @Test fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() = testScope.runTest { - kosmos.setCommunalEnabled(false) + kosmos.setCommunalV2Enabled(false) runCurrent() assertThat( @@ -143,7 +146,8 @@ class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : Sy @Parameters(name = "{0}") fun getParams(): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( - Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON + Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, + Flags.FLAG_GLANCEABLE_HUB_V2, ) .andSceneContainer() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index e07961959f1b..635f80262348 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -995,12 +995,12 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen)) val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) - sendSteps(sendStep1) - kosmos.setSceneTransition(Idle(Scenes.Gone)) val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + sendSteps(sendStep1, sendStep2) + kosmos.setSceneTransition(Idle(Scenes.Gone)) val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED) val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) - sendSteps(sendStep2, sendStep3, sendStep4) + sendSteps(sendStep3, sendStep4) assertEquals(listOf<TransitionStep>(), currentStatesMapped) } @@ -1134,6 +1134,63 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) } + @Test + @EnableSceneContainer + fun transition_filter_on_belongsToInstantReversedTransition_out_of_lockscreen_scene() = + testScope.runTest { + val currentStatesMapped by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, Scenes.Gone))) + + kosmos.setSceneTransition(Transition(Scenes.Gone, Scenes.Lockscreen)) + val sendStep1 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED) + kosmos.setSceneTransition(Idle(Scenes.Gone)) + val sendStep2 = TransitionStep(UNDEFINED, LOCKSCREEN, 0.6f, CANCELED) + sendSteps(sendStep1, sendStep2) + val sendStep3 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep4 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + sendSteps(sendStep3, sendStep4) + + assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_filter_on_belongsToInstantReversedTransition_into_lockscreen_scene() = + testScope.runTest { + val currentStatesMapped by + collectValues(underTest.transition(Edge.create(Scenes.Gone, LOCKSCREEN))) + + kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone)) + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + kosmos.setSceneTransition(Idle(Scenes.Lockscreen)) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED) + sendSteps(sendStep1, sendStep2) + val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED) + val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED) + sendSteps(sendStep3, sendStep4) + + assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_filter_on_belongsToInstantReversedTransition_out_of_ls_with_wildcard() = + testScope.runTest { + val currentStatesMapped by + collectValues(underTest.transition(Edge.create(to = LOCKSCREEN))) + + kosmos.setSceneTransition(Transition(Scenes.Lockscreen, Scenes.Gone)) + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + kosmos.setSceneTransition(Idle(Scenes.Lockscreen)) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 0.6f, CANCELED) + sendSteps(sendStep1, sendStep2) + val sendStep3 = TransitionStep(UNDEFINED, LOCKSCREEN, 0f, STARTED) + val sendStep4 = TransitionStep(UNDEFINED, LOCKSCREEN, 1f, FINISHED) + sendSteps(sendStep3, sendStep4) + + assertEquals(listOf(sendStep3, sendStep4), currentStatesMapped) + } + private suspend fun sendSteps(vararg steps: TransitionStep) { steps.forEach { repository.sendTransitionStep(it) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 111b3b65c05d..4457d9b25ade 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.qs.fgsManagerController import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor import com.android.systemui.qs.panels.ui.viewmodel.setConfigurationForMediaInRow import com.android.systemui.res.R +import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository @@ -436,6 +437,28 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun qsVisibleAndAnyShadeVisible() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest.isQsVisible = false + fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false) + assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse() + + underTest.isQsVisible = true + fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false) + assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse() + + underTest.isQsVisible = false + fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true) + assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isFalse() + + underTest.isQsVisible = true + fakeShadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true) + assertThat(underTest.isQsVisibleAndAnyShadeExpanded).isTrue() + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt new file mode 100644 index 000000000000..eef195b56188 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GridLayoutTypeRepositoryTest : SysuiTestCase() { + val kosmos = testKosmos() + + val underTest = kosmos.gridLayoutTypeRepository + + @Test + fun defaultType_paginated() = + kosmos.runTest { + val type by collectLastValue(underTest.defaultLayoutType) + + assertThat(type).isEqualTo(PaginatedGridLayoutType) + } + + @Test + fun dualShadeType_infinite() = + kosmos.runTest { + val type by collectLastValue(underTest.dualShadeLayoutType) + + assertThat(type).isEqualTo(InfiniteGridLayoutType) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt new file mode 100644 index 000000000000..b5915386b443 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shared.flag.DualShade +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GridLayoutTypeInteractorTest : SysuiTestCase() { + val kosmos = testKosmos() + + val Kosmos.underTest by Kosmos.Fixture { kosmos.gridLayoutTypeInteractor } + + @DisableFlags(DualShade.FLAG_NAME) + @Test + fun noDualShade_gridAlwaysPaginated() = + kosmos.runTest { + val type by collectLastValue(underTest.layout) + + fakeShadeRepository.setShadeLayoutWide(false) + assertThat(type).isEqualTo(PaginatedGridLayoutType) + + fakeShadeRepository.setShadeLayoutWide(true) + assertThat(type).isEqualTo(PaginatedGridLayoutType) + } + + @EnableFlags(DualShade.FLAG_NAME) + @Test + fun dualShade_gridAlwaysInfinite() = + kosmos.runTest { + val type by collectLastValue(underTest.layout) + + fakeShadeRepository.setShadeLayoutWide(false) + assertThat(type).isEqualTo(InfiniteGridLayoutType) + + fakeShadeRepository.setShadeLayoutWide(true) + assertThat(type).isEqualTo(InfiniteGridLayoutType) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt index a9a527fb8df6..4891c9fb6def 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository -import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt index f2bfd729f74a..a8e390c25a4d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index 3f4a13414e96..b921ff7063a5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -18,8 +18,9 @@ package com.android.systemui.qs.tiles import android.net.ConnectivityManager import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.telephony.flags.Flags @@ -31,6 +32,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -51,11 +53,17 @@ import org.mockito.MockitoAnnotations import org.mockito.kotlin.any import org.mockito.kotlin.times import org.mockito.kotlin.verify +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class AirplaneModeTileTest : SysuiTestCase() { +class AirplaneModeTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Mock private lateinit var mHost: QSHost @Mock private lateinit var mMetricsLogger: MetricsLogger @@ -149,4 +157,12 @@ class AirplaneModeTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index ae003497f9e9..cf9ef4d7c2a2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -18,9 +18,10 @@ package com.android.systemui.qs.tiles import android.content.Context import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -31,6 +32,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -51,14 +53,26 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest -class BatterySaverTileTest : SysuiTestCase() { +class BatterySaverTileTest(flagsParameterization: FlagsParameterization) : SysuiTestCase() { companion object { private const val USER = 10 + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flagsParameterization) } @Mock private lateinit var userContext: Context diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 31519c584daa..6bb671315640 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt @@ -17,10 +17,11 @@ package com.android.systemui.qs.tiles import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.provider.Settings import android.safetycenter.SafetyCenterManager import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -30,6 +31,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLoggerFake +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -45,15 +47,27 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class CameraToggleTileTest : SysuiTestCase() { +class CameraToggleTileTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { /* isBlocked */ const val CAMERA_TOGGLE_ENABLED: Boolean = false const val CAMERA_TOGGLE_DISABLED: Boolean = true + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) } @Mock private lateinit var host: QSHost diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index 2c796a93613a..a58dd6301e07 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -16,6 +16,10 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -25,10 +29,10 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.os.Handler; +import android.platform.test.flag.junit.FlagsParameterization; import android.provider.Settings; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -55,13 +59,23 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorInversionTileTest extends SysuiTestCase { private static final Integer COLOR_INVERSION_DISABLED = 0; private static final Integer COLOR_INVERSION_ENABLED = 1; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private QSHost mHost; @Mock @@ -81,6 +95,11 @@ public class ColorInversionTileTest extends SysuiTestCase { private SecureSettings mSecureSettings; private ColorInversionTile mTile; + public ColorInversionTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index 23be9da106d9..27fd2818ea88 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -17,8 +17,9 @@ package com.android.systemui.qs.tiles import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -29,6 +30,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -44,11 +46,17 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class DataSaverTileTest : SysuiTestCase() { +class DataSaverTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Mock private lateinit var mHost: QSHost @Mock private lateinit var mMetricsLogger: MetricsLogger @@ -121,4 +129,12 @@ class DataSaverTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index 33748b973f1c..9e4cf94df3de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -20,11 +20,14 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Handler +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.provider.Settings import android.service.quicksettings.Tile import android.testing.TestableLooper import androidx.lifecycle.LifecycleOwner -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -43,7 +46,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture @@ -67,11 +72,17 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -class DeviceControlsTileTest : SysuiTestCase() { +class DeviceControlsTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @@ -151,7 +162,7 @@ class DeviceControlsTileTest : SysuiTestCase() { } `when`(controlsComponent.getTileTitleId()).thenReturn(R.string.quick_controls_title) - `when`(controlsComponent.getTileTitleId()).thenReturn(R.drawable.controls_icon) + `when`(controlsComponent.getTileImageId()).thenReturn(R.drawable.controls_icon) } @Test @@ -378,6 +389,28 @@ class DeviceControlsTileTest : SysuiTestCase() { assertThat(tile.tileLabel).isEqualTo(context.getText(controlsComponent.getTileTitleId())) } + @Test + @DisableFlags(QSComposeFragment.FLAG_NAME) + fun tileIconEqualsResourceFromComponent_composeFlagDisabled() { + tile.refreshState() + testableLooper.processAllMessages() + assertThat(tile.state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.controls_icon)) + } + + @Test + @EnableFlags(QSComposeFragment.FLAG_NAME) + fun tileIconEqualsResourceFromComponent_composeFlagEnable() { + tile.refreshState() + testableLooper.processAllMessages() + assertThat(tile.state.icon) + .isEqualTo( + QSTileImpl.DrawableIconWithRes( + mContext.getDrawable(R.drawable.controls_icon), + R.drawable.controls_icon, + ) + ) + } + private fun createTile(): DeviceControlsTile { return DeviceControlsTile( qsHost, @@ -396,6 +429,14 @@ class DeviceControlsTileTest : SysuiTestCase() { testableLooper.processAllMessages() } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } private const val CONTROLS_ACTIVITY_CLASS_NAME = "com.android.systemui.controls.ui.ControlsActivity" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt index 3cb9091459d9..6a15a5bc21e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.qs.tiles -import android.content.Context import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -13,6 +13,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -27,13 +28,17 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class FlashlightTileTest : SysuiTestCase() { +class FlashlightTileTest(flags: FlagsParameterization) : SysuiTestCase() { - @Mock private lateinit var mockContext: Context + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Mock private lateinit var qsLogger: QSLogger @@ -58,7 +63,7 @@ class FlashlightTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - Mockito.`when`(qsHost.context).thenReturn(mockContext) + Mockito.`when`(qsHost.context).thenReturn(mContext) tile = FlashlightTile( @@ -122,4 +127,12 @@ class FlashlightTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index f33de4d9144d..eeccbdf20540 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -17,10 +17,11 @@ package com.android.systemui.qs.tiles import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.service.quicksettings.Tile import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -29,6 +30,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.dialog.WifiStateWorker @@ -62,12 +64,18 @@ import org.mockito.kotlin.eq import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidJUnit4::class) -class InternetTileNewImplTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() { + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + lateinit var underTest: InternetTileNewImpl private val testDispatcher = StandardTestDispatcher() @@ -252,5 +260,11 @@ class InternetTileNewImplTest : SysuiTestCase() { const val WIFI_SSID = "test ssid" val ACTIVE_WIFI = WifiNetworkModel.Active.of(isValidated = true, level = 4, ssid = WIFI_SSID) + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java index b5a64b39f7ce..d7b183ed7def 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -16,6 +16,10 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; @@ -25,10 +29,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Handler; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.Tile; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -57,11 +61,21 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class InternetTileTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private QSHost mHost; @Mock @@ -78,6 +92,11 @@ public class InternetTileTest extends SysuiTestCase { private TestableLooper mTestableLooper; private InternetTile mTile; + public InternetTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 4be189964d6b..a581b57ac44f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.qs.tiles -import android.content.Context import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -29,6 +29,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor @@ -48,13 +49,18 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class LocationTileTest : SysuiTestCase() { +class LocationTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } - @Mock private lateinit var mockContext: Context @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @@ -73,7 +79,7 @@ class LocationTileTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - `when`(qsHost.context).thenReturn(mockContext) + `when`(qsHost.context).thenReturn(mContext) tile = LocationTile( @@ -139,4 +145,12 @@ class LocationTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index afe9713538de..a39692d10863 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt @@ -17,10 +17,11 @@ package com.android.systemui.qs.tiles import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.provider.Settings import android.safetycenter.SafetyCenterManager import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -30,6 +31,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -45,15 +47,27 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class MicrophoneToggleTileTest : SysuiTestCase() { +class MicrophoneToggleTileTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { /* isBlocked */ const val MICROPHONE_TOGGLE_ENABLED: Boolean = false const val MICROPHONE_TOGGLE_DISABLED: Boolean = true + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) } @Mock private lateinit var host: QSHost diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt index 69dab39b279c..a193cbcec114 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -19,8 +19,9 @@ package com.android.systemui.qs.tiles import android.hardware.display.ColorDisplayManager import android.hardware.display.NightDisplayListener import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -31,6 +32,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -46,11 +48,18 @@ import org.mockito.Mock import org.mockito.Mockito.anyInt import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class NightDisplayTileTest : SysuiTestCase() { +class NightDisplayTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Mock private lateinit var mHost: QSHost @Mock private lateinit var mMetricsLogger: MetricsLogger @@ -135,4 +144,12 @@ class NightDisplayTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 2345128e8c84..c24498411ff7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -16,6 +16,10 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static junit.framework.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -28,10 +32,10 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.Tile; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -59,10 +63,21 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ReduceBrightColorsTileTest extends SysuiTestCase { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private QSHost mHost; @Mock @@ -85,6 +100,11 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { private TestableLooper mTestableLooper; private ReduceBrightColorsTile mTile; + public ReduceBrightColorsTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index 7fb0eabe96e5..fee358a7c15d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -17,6 +17,9 @@ package com.android.systemui.qs.tiles; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; import static junit.framework.TestCase.assertEquals; @@ -27,10 +30,10 @@ import android.Manifest; import android.content.pm.PackageManager; import android.hardware.SensorPrivacyManager; import android.os.Handler; +import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; import android.testing.TestableResources; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -59,7 +62,12 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class RotationLockTileTest extends SysuiTestCase { @@ -70,6 +78,11 @@ public class RotationLockTileTest extends SysuiTestCase { "1:2" }; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private PackageManager mPackageManager; @Mock @@ -98,6 +111,11 @@ public class RotationLockTileTest extends SysuiTestCase { private RotationLockTile mLockTile; private TestableResources mTestableResources; + public RotationLockTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index a7c7a78dae5f..fc1d73b62abd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -16,6 +16,10 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; @@ -31,10 +35,10 @@ import static org.mockito.Mockito.when; import android.app.Dialog; import android.media.projection.StopReason; import android.os.Handler; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.Tile; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -66,11 +70,21 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ScreenRecordTileTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private RecordingController mController; @Mock @@ -105,6 +119,11 @@ public class ScreenRecordTileTest extends SysuiTestCase { private TestableLooper mTestableLooper; private ScreenRecordTile mTile; + public ScreenRecordTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index 773e225a6a35..3246e6490799 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -17,12 +17,11 @@ package com.android.systemui.qs.tiles import android.app.UiModeManager -import android.content.Context import android.content.res.Configuration -import android.content.res.Resources import android.os.Handler +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -32,6 +31,7 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl @@ -48,15 +48,19 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class UiModeNightTileTest : SysuiTestCase() { +class UiModeNightTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } - @Mock private lateinit var mockContext: Context @Mock private lateinit var uiModeManager: UiModeManager - @Mock private lateinit var resources: Resources @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @@ -70,19 +74,19 @@ class UiModeNightTileTest : SysuiTestCase() { private val falsingManager = FalsingManagerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: UiModeNightTile - private lateinit var configuration: Configuration + private val configuration = Configuration() @Before fun setUp() { MockitoAnnotations.initMocks(this) + val initialConfiguration = mContext.resources.configuration + onTeardown { mContext.resources.configuration.updateFrom(initialConfiguration) } + testableLooper = TestableLooper.get(this) - configuration = Configuration() mContext.addMockSystemService(UiModeManager::class.java, uiModeManager) - `when`(qsHost.context).thenReturn(mockContext) + `when`(qsHost.context).thenReturn(mContext) `when`(qsHost.userContext).thenReturn(mContext) - `when`(mockContext.resources).thenReturn(resources) - `when`(resources.configuration).thenReturn(configuration) tile = UiModeNightTile( @@ -118,7 +122,7 @@ class UiModeNightTileTest : SysuiTestCase() { } @Test - fun testIcon_whenNightModeOn_isOffState() { + fun testIcon_whenNightModeOff_isOffState() { val state = QSTile.BooleanState() setNightModeOff() @@ -131,11 +135,13 @@ class UiModeNightTileTest : SysuiTestCase() { private fun setNightModeOn() { `when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_YES) configuration.uiMode = Configuration.UI_MODE_NIGHT_YES + mContext.resources.configuration.updateFrom(configuration) } private fun setNightModeOff() { `when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_NO) configuration.uiMode = Configuration.UI_MODE_NIGHT_NO + mContext.resources.configuration.updateFrom(configuration) } private fun createExpectedIcon(resId: Int): QSTile.Icon { @@ -145,4 +151,12 @@ class UiModeNightTileTest : SysuiTestCase() { QSTileImpl.ResourceIcon.get(resId) } } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt index 52c476ec92cc..e4a988860a6e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.connectivity.AccessPointController import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -136,4 +137,13 @@ class InternetTileUserActionInteractorTest : SysuiTestCase() { verify(wifiStateWorker, times(1)).isWifiEnabled = eq(true) } + + @Test + fun detailsViewModel() = + kosmos.testScope.runTest { + assertThat(underTest.detailsViewModel.getTitle()) + .isEqualTo("Internet") + assertThat(underTest.detailsViewModel.getSubTitle()) + .isEqualTo("Tab a network to connect") + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt index 954215eede0d..2edb9c60711b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -173,6 +173,21 @@ class QSTileViewModelTest : SysuiTestCase() { .isEqualTo(FakeQSTileDataInteractor.AvailabilityRequest(USER)) } + @Test + fun tileDetails() = + testScope.runTest { + assertThat(tileUserActionInteractor.detailsViewModel).isNotNull() + assertThat(tileUserActionInteractor.detailsViewModel?.getTitle()) + .isEqualTo("FakeQSTileUserActionInteractor") + assertThat(underTest.detailsViewModel).isNotNull() + assertThat(underTest.detailsViewModel?.getTitle()) + .isEqualTo("FakeQSTileUserActionInteractor") + + tileUserActionInteractor.detailsViewModel = null + assertThat(tileUserActionInteractor.detailsViewModel).isNull() + assertThat(underTest.detailsViewModel).isNull() + } + private fun createViewModel( scope: TestScope, config: QSTileConfig = tileConfig, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt index 3ebf9f72b5ff..a62d9d5ce62f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel @@ -163,6 +164,28 @@ class CallChipViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME) + fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() = + testScope.runTest { + val latest by collectLastValue(underTest.chip) + + val notifKey = "testNotifKey" + repo.setOngoingCallState( + inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey) + ) + + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isInstanceOf( + OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java + ) + val actualNotifKey = + (((latest as OngoingActivityChipModel.Shown).icon) + as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon) + .notificationKey + assertThat(actualNotifKey).isEqualTo(notifKey) + } + + @Test @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON) fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt index e96dd16e9023..f06bab756acc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.chips.notification.domain.interactor +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -25,6 +26,8 @@ import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -111,6 +114,27 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_cdEnabled_missingStatusBarIconChipView_inConstructor_emitsNotNull() = + kosmos.runTest { + val underTest = + factory.create( + activeNotificationModel( + key = "notif1", + statusBarChipIcon = null, + whenTime = 123L, + ) + ) + + val latest by collectLastValue(underTest.notificationChip) + + assertThat(latest) + .isEqualTo( + NotificationChipModel("notif1", statusBarChipIconView = null, whenTime = 123L) + ) + } + + @Test fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() = kosmos.runTest { val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock()) @@ -126,6 +150,29 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() = + kosmos.runTest { + val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock()) + val underTest = factory.create(startingNotif) + val latest by collectLastValue(underTest.notificationChip) + assertThat(latest).isNotNull() + + underTest.setNotification( + activeNotificationModel(key = "notif1", statusBarChipIcon = null, whenTime = 123L) + ) + + assertThat(latest) + .isEqualTo( + NotificationChipModel( + key = "notif1", + statusBarChipIconView = null, + whenTime = 123L, + ) + ) + } + + @Test fun notificationChip_appIsVisibleOnCreation_emitsNull() = kosmos.runTest { activityManagerRepository.fake.startingIsAppVisibleValue = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 11831ca97389..8a4ddceb0d3a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.data.model.activeNotificationModel import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -107,6 +108,30 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val notifKey = "notif" + setNotifs( + listOf( + activeNotificationModel( + key = notifKey, + statusBarChipIcon = null, + promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(), + ) + ) + ) + + assertThat(latest).hasSize(1) + val chip = latest!![0] + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(chip.icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey)) + } + + @Test fun chips_onlyForPromotedNotifs() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -139,6 +164,41 @@ class NotifChipsViewModelTest : SysuiTestCase() { } @Test + @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) + fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() = + kosmos.runTest { + val latest by collectLastValue(underTest.chips) + + val firstKey = "notif1" + val secondKey = "notif2" + val thirdKey = "notif3" + setNotifs( + listOf( + activeNotificationModel( + key = firstKey, + statusBarChipIcon = null, + promotedContent = PromotedNotificationContentModel.Builder(firstKey).build(), + ), + activeNotificationModel( + key = secondKey, + statusBarChipIcon = null, + promotedContent = + PromotedNotificationContentModel.Builder(secondKey).build(), + ), + activeNotificationModel( + key = thirdKey, + statusBarChipIcon = null, + promotedContent = null, + ), + ) + ) + + assertThat(latest).hasSize(2) + assertIsNotifKey(latest!![0], firstKey) + assertIsNotifKey(latest!![1], secondKey) + } + + @Test fun chips_clickingChipNotifiesInteractor() = kosmos.runTest { val latest by collectLastValue(underTest.chips) @@ -178,5 +238,12 @@ class NotifChipsViewModelTest : SysuiTestCase() { assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) } + + fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) { + assertThat(latest) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey)) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt index 689fc7cb647b..9e7befd3cf92 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification @@ -22,671 +24,809 @@ import android.app.NotificationManager import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.service.notification.StatusBarNotification -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.keyguardUpdateMonitor import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.SysuiTestCase -import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.statusbar.fakeStatusBarStateController +import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.RankingBuilder import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.DynamicPrivacyController import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.statusbar.notification.collection.notifPipeline +import com.android.systemui.statusbar.notification.dynamicPrivacyController +import com.android.systemui.statusbar.notification.mockDynamicPrivacyController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController +import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController +import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController import com.android.systemui.testKosmos -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor -import dagger.BindsInstance -import dagger.Component -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +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.Test import org.junit.runner.RunWith -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever +import org.mockito.kotlin.any +import org.mockito.kotlin.clearInvocations +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class SensitiveContentCoordinatorTest : SysuiTestCase() { - - val kosmos = testKosmos() - - val dynamicPrivacyController: DynamicPrivacyController = mock() - val lockscreenUserManager: NotificationLockscreenUserManager = mock() - val pipeline: NotifPipeline = mock() - val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock() - val statusBarStateController: StatusBarStateController = mock() - val keyguardStateController: KeyguardStateController = mock() - val mSelectedUserInteractor: SelectedUserInteractor = mock() +@RunWith(ParameterizedAndroidJunit4::class) +class SensitiveContentCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { + + val kosmos = + testKosmos().apply { + // Override some Kosmos objects with mocks or fakes for easier testability + dynamicPrivacyController = mockDynamicPrivacyController + sensitiveNotificationProtectionController = + mockSensitiveNotificationProtectionController + statusBarStateController = fakeStatusBarStateController + } + + val testScope = kosmos.testScope + + val dynamicPrivacyController: DynamicPrivacyController = kosmos.mockDynamicPrivacyController + val lockscreenUserManager: NotificationLockscreenUserManager = + kosmos.notificationLockscreenUserManager + val pipeline: NotifPipeline = kosmos.notifPipeline + val keyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor + val statusBarStateController: SysuiStatusBarStateController = + kosmos.fakeStatusBarStateController val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController = - mock() - val deviceEntryInteractor: DeviceEntryInteractor = mock() - val sceneInteractor: SceneInteractor = mock() - - val coordinator: SensitiveContentCoordinator = - DaggerTestSensitiveContentCoordinatorComponent.factory() - .create( - dynamicPrivacyController, - lockscreenUserManager, - keyguardUpdateMonitor, - statusBarStateController, - keyguardStateController, - mSelectedUserInteractor, - sensitiveNotificationProtectionController, - deviceEntryInteractor, - sceneInteractor, - kosmos.applicationCoroutineScope, - ) - .coordinator + kosmos.mockSensitiveNotificationProtectionController + val deviceEntryInteractor: DeviceEntryInteractor + get() = kosmos.deviceEntryInteractor - @Test - fun onDynamicPrivacyChanged_invokeInvalidationListener() { - coordinator.attach(pipeline) - val invalidator = - withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } - val dynamicPrivacyListener = - withArgCaptor<DynamicPrivacyController.Listener> { - verify(dynamicPrivacyController).addListener(capture()) - } + val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository + val sceneInteractor: SceneInteractor + get() = kosmos.sceneInteractor - val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() - invalidator.setInvalidationListener(invalidationListener) + val coordinator: SensitiveContentCoordinator by lazy { kosmos.sensitiveContentCoordinator } - dynamicPrivacyListener.onDynamicPrivacyChanged() + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } - verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any()) + init { + mSetFlagsRule.setFlagsParameterization(flags) } @Test - @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onSensitiveStateChanged_invokeInvalidationListener() { - coordinator.attach(pipeline) - val invalidator = - withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } - val onSensitiveStateChangedListener = - withArgCaptor<Runnable> { - verify(sensitiveNotificationProtectionController) - .registerSensitiveStateListener(capture()) - } - - val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() - invalidator.setInvalidationListener(invalidationListener) + @EnableSceneContainer + fun onLockscreenDynamicallyUnlocked_invokeInvalidationListener() = + testScope.runTest { + // Setup + coordinator.attach(pipeline) + val invalidator = + withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } + val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() + invalidator.setInvalidationListener(invalidationListener) + + // Given the lockscreen is shown + setupLockScreen(canSwipeUp = false) + clearInvocations(invalidationListener) + + // When the device gets unlocked + faceAuthRepository.isAuthenticated.value = true + runCurrent() + + // Then the invalidationListener is called + verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any()) + } - onSensitiveStateChangedListener.run() + @Test + @DisableSceneContainer + fun onDynamicPrivacyChanged_invokeInvalidationListener() = + testScope.runTest { + coordinator.attach(pipeline) + val invalidator = + withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } + val dynamicPrivacyListener = + withArgCaptor<DynamicPrivacyController.Listener> { + verify(dynamicPrivacyController).addListener(capture()) + } + + val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() + invalidator.setInvalidationListener(invalidationListener) + + dynamicPrivacyListener.onDynamicPrivacyChanged() + + verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any()) + } - verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any()) - } + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) + fun onSensitiveStateChanged_invokeInvalidationListener() = + testScope.runTest { + coordinator.attach(pipeline) + val invalidator = + withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) } + val onSensitiveStateChangedListener = + withArgCaptor<Runnable> { + verify(sensitiveNotificationProtectionController) + .registerSensitiveStateListener(capture()) + } + + val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>() + invalidator.setInvalidationListener(invalidationListener) + + onSensitiveStateChangedListener.run() + + verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any()) + } @Test @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun screenshareSecretFilter_flagDisabled_filterNoAdded() { - coordinator.attach(pipeline) + fun screenshareSecretFilter_flagDisabled_filterNoAdded() = + testScope.runTest { + coordinator.attach(pipeline) - verify(pipeline, never()).addFinalizeFilter(any(NotifFilter::class.java)) - } + verify(pipeline, never()).addFinalizeFilter(any()) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun screenshareSecretFilter_sensitiveInctive_noFiltersSecret() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false) - - coordinator.attach(pipeline) - val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) } - - val defaultNotification = createNotificationEntry("test", false, false) - val notificationWithSecretVisibility = createNotificationEntry("test", true, false) - val notificationOnSecretChannel = createNotificationEntry("test", false, true) - - assertFalse(filter.shouldFilterOut(defaultNotification, 0)) - assertFalse(filter.shouldFilterOut(notificationWithSecretVisibility, 0)) - assertFalse(filter.shouldFilterOut(notificationOnSecretChannel, 0)) - } + fun screenshareSecretFilter_sensitiveInctive_noFiltersSecret() = + testScope.runTest { + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(false) + + coordinator.attach(pipeline) + val filter = + withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) } + + val defaultNotification = createNotificationEntry("test", false, false) + val notificationWithSecretVisibility = createNotificationEntry("test", true, false) + val notificationOnSecretChannel = createNotificationEntry("test", false, true) + + assertFalse(filter.shouldFilterOut(defaultNotification, 0)) + assertFalse(filter.shouldFilterOut(notificationWithSecretVisibility, 0)) + assertFalse(filter.shouldFilterOut(notificationOnSecretChannel, 0)) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun screenshareSecretFilter_sensitiveActive_filtersSecret() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - coordinator.attach(pipeline) - val filter = withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) } - - val defaultNotification = createNotificationEntry("test", false, false) - val notificationWithSecretVisibility = createNotificationEntry("test", true, false) - val notificationOnSecretChannel = createNotificationEntry("test", false, true) - - assertFalse(filter.shouldFilterOut(defaultNotification, 0)) - assertTrue(filter.shouldFilterOut(notificationWithSecretVisibility, 0)) - assertTrue(filter.shouldFilterOut(notificationOnSecretChannel, 0)) - } + fun screenshareSecretFilter_sensitiveActive_filtersSecret() = + testScope.runTest { + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + coordinator.attach(pipeline) + val filter = + withArgCaptor<NotifFilter> { verify(pipeline).addFinalizeFilter(capture()) } + + val defaultNotification = createNotificationEntry("test", false, false) + val notificationWithSecretVisibility = createNotificationEntry("test", true, false) + val notificationOnSecretChannel = createNotificationEntry("test", false, true) + + assertFalse(filter.shouldFilterOut(defaultNotification, 0)) + assertTrue(filter.shouldFilterOut(notificationWithSecretVisibility, 0)) + assertTrue(filter.shouldFilterOut(notificationOnSecretChannel, 0)) + } @Test - fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, false) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, false) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, false) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, false) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, false) - } + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, false) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(true) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, false) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, false) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - } + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Suppress("ktlint:standard:max-line-length") - fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Suppress("ktlint:standard:max-line-length") - fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, false) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, false) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - } + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false) - val entry = fakeNotification(1, true) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = false) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - val entry = fakeNotification(1, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - } + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + val entry = fakeNotification(1, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - val entry = fakeNotification(1, true) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(false, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + val entry = fakeNotification(1, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(false, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Suppress("ktlint:standard:max-line-length") - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - val entry = fakeNotification(1, true) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + val entry = fakeNotification(1, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) - val entry = fakeNotification(2, true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - } + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) - val entry = fakeNotification(2, true) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) - } + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(true) + } @Test @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Suppress("ktlint:standard:max-line-length") - fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } - - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) - val entry = fakeNotification(2, true) - whenever( - sensitiveNotificationProtectionController.shouldProtectNotification( - entry.getRepresentativeEntry() + fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true) + val entry = fakeNotification(2, true) + whenever( + sensitiveNotificationProtectionController.shouldProtectNotification( + entry.getRepresentativeEntry() + ) ) - ) - .thenReturn(true) + .thenReturn(true) - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - verify(entry.representativeEntry!!).setSensitive(true, true) - verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) - } + verify(entry.representativeEntry!!).setSensitive(true, true) + verify(entry.representativeEntry!!.row!!).setPublicExpanderVisible(false) + } @Test - fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() { - coordinator.attach(pipeline) - val onBeforeRenderListListener = - withArgCaptor<OnBeforeRenderListListener> { - verify(pipeline).addOnBeforeRenderListListener(capture()) - } + fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() = + testScope.runTest { + coordinator.attach(pipeline) + val onBeforeRenderListListener = + withArgCaptor<OnBeforeRenderListListener> { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } + + whenever(lockscreenUserManager.currentUserId).thenReturn(1) + whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) + whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)) + .thenReturn(false) + setupLockScreen(canSwipeUp = true) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) + .thenReturn(true) + val entry = fakeNotification(2, true) + whenever(sensitiveNotificationProtectionController.isSensitiveStateActive) + .thenReturn(true) + whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any())) + .thenReturn(true) + statusBarStateController.state = StatusBarState.KEYGUARD + + onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) + + verify(entry.representativeEntry!!, never()).setSensitive(any(), any()) + verify(entry.representativeEntry!!.row!!, never()).setPublicExpanderVisible(any()) + } - whenever(lockscreenUserManager.currentUserId).thenReturn(1) - whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true) - whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false) - whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true) - whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD) - whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any())) - .thenReturn(true) - val entry = fakeNotification(2, true) - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any())) - .thenReturn(true) - - onBeforeRenderListListener.onBeforeRenderList(listOf(entry)) - - verify(entry.representativeEntry!!, never()).setSensitive(any(), any()) - verify(entry.representativeEntry!!.row!!, never()).setPublicExpanderVisible(any()) + private fun TestScope.setupLockScreen(canSwipeUp: Boolean) { + if (SceneContainerFlag.isEnabled) { + val authMethod = + if (canSwipeUp) AuthenticationMethodModel.None + else AuthenticationMethodModel.Password + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + switchToScene(Scenes.Lockscreen) + } else { + whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(canSwipeUp) + } + } + + private fun TestScope.switchToScene(sceneKey: SceneKey) { + sceneInteractor.changeScene(sceneKey, "reason") + sceneInteractor.setTransitionState(flowOf(ObservableTransitionState.Idle(sceneKey))) + runCurrent() } private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry { @@ -733,26 +873,3 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() { return notificationEntry } } - -@CoordinatorScope -@Component(modules = [SensitiveContentCoordinatorModule::class]) -interface TestSensitiveContentCoordinatorComponent { - val coordinator: SensitiveContentCoordinator - - @Component.Factory - interface Factory { - fun create( - @BindsInstance dynamicPrivacyController: DynamicPrivacyController, - @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager, - @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor, - @BindsInstance statusBarStateController: StatusBarStateController, - @BindsInstance keyguardStateController: KeyguardStateController, - @BindsInstance selectedUserInteractor: SelectedUserInteractor, - @BindsInstance - sensitiveNotificationProtectionController: SensitiveNotificationProtectionController, - @BindsInstance deviceEntryInteractor: DeviceEntryInteractor, - @BindsInstance sceneInteractor: SceneInteractor, - @BindsInstance @Application scope: CoroutineScope, - ): TestSensitiveContentCoordinatorComponent - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt deleted file mode 100644 index 8b4f53a88a6f..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt +++ /dev/null @@ -1,698 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.statusbar.notification.headsup - -import android.app.Notification -import android.app.PendingIntent -import android.app.Person -import android.os.Handler -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.FlagsParameterization -import android.testing.TestableLooper.RunWithLooper -import androidx.test.filters.SmallTest -import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager -import com.android.systemui.kosmos.KosmosJavaAdapter -import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.res.R -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.notification.collection.NotificationEntry -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder -import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl -import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow -import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper -import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.concurrency.mockExecutorHandler -import com.android.systemui.util.kotlin.JavaAdapter -import com.android.systemui.util.settings.FakeGlobalSettings -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.MutableStateFlow -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.invocation.InvocationOnMock -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule -import org.mockito.kotlin.eq -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters - -@SmallTest -@RunWithLooper -@RunWith(ParameterizedAndroidJunit4::class) -// TODO(b/378142453): Merge this with HeadsUpManagerImplTest. -open class HeadsUpManagerImplOldTest(flags: FlagsParameterization?) : SysuiTestCase() { - protected var mKosmos: KosmosJavaAdapter = KosmosJavaAdapter(this) - - @JvmField @Rule var rule: MockitoRule = MockitoJUnit.rule() - - private val mUiEventLoggerFake = UiEventLoggerFake() - - private val mLogger: HeadsUpManagerLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer())) - - @Mock private val mBgHandler: Handler? = null - - @Mock private val dumpManager: DumpManager? = null - - @Mock private val mShadeInteractor: ShadeInteractor? = null - private var mAvalancheController: AvalancheController? = null - - @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null - - protected val globalSettings: FakeGlobalSettings = FakeGlobalSettings() - protected val systemClock: FakeSystemClock = FakeSystemClock() - protected val executor: FakeExecutor = FakeExecutor(systemClock) - - @Mock protected var mRow: ExpandableNotificationRow? = null - - private fun createHeadsUpManager(): HeadsUpManagerImpl { - return HeadsUpManagerImpl( - mContext, - mLogger, - mKosmos.statusBarStateController, - mKosmos.keyguardBypassController, - GroupMembershipManagerImpl(), - mKosmos.visualStabilityProvider, - mKosmos.configurationController, - mockExecutorHandler(executor), - globalSettings, - systemClock, - executor, - mAccessibilityMgr, - mUiEventLoggerFake, - JavaAdapter(mKosmos.testScope), - mShadeInteractor, - mAvalancheController, - ) - } - - private fun createStickyEntry(id: Int): NotificationEntry { - val notif = - Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setFullScreenIntent( - Mockito.mock(PendingIntent::class.java), /* highPriority */ - true, - ) - .build() - return HeadsUpManagerTestUtil.createEntry(id, notif) - } - - private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry { - val notif = - Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true) - .build() - return HeadsUpManagerTestUtil.createEntry(id, notif) - } - - private fun useAccessibilityTimeout(use: Boolean) { - if (use) { - Mockito.doReturn(TEST_A11Y_AUTO_DISMISS_TIME) - .`when`(mAccessibilityMgr!!) - .getRecommendedTimeoutMillis(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()) - } else { - Mockito.`when`( - mAccessibilityMgr!!.getRecommendedTimeoutMillis( - ArgumentMatchers.anyInt(), - ArgumentMatchers.anyInt(), - ) - ) - .then { i: InvocationOnMock -> i.getArgument(0) } - } - } - - init { - mSetFlagsRule.setFlagsParameterization(flags!!) - } - - @Throws(Exception::class) - override fun SysuiSetup() { - super.SysuiSetup() - mContext.getOrCreateTestableResources().apply { - this.addOverride(R.integer.ambient_notification_extension_time, TEST_EXTENSION_TIME) - this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME) - this.addOverride( - R.integer.heads_up_notification_minimum_time, - TEST_MINIMUM_DISPLAY_TIME, - ) - this.addOverride( - R.integer.heads_up_notification_minimum_time_with_throttling, - TEST_MINIMUM_DISPLAY_TIME, - ) - this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME) - this.addOverride( - R.integer.sticky_heads_up_notification_time, - TEST_STICKY_AUTO_DISMISS_TIME, - ) - } - - mAvalancheController = - AvalancheController(dumpManager!!, mUiEventLoggerFake, mLogger, mBgHandler!!) - Mockito.`when`(mShadeInteractor!!.isAnyExpanded).thenReturn(MutableStateFlow(true)) - Mockito.`when`(mKosmos.keyguardBypassController.bypassEnabled).thenReturn(false) - } - - @Test - fun testHasNotifications_headsUpManagerMapNotEmpty_true() { - val bhum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - bhum.showNotification(entry) - - Truth.assertThat(bhum.mHeadsUpEntryMap).isNotEmpty() - Truth.assertThat(bhum.hasNotifications()).isTrue() - } - - @Test - @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testHasNotifications_avalancheMapNotEmpty_true() { - val bhum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val headsUpEntry = bhum.createHeadsUpEntry(notifEntry) - mAvalancheController!!.addToNext(headsUpEntry) {} - - Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isNotEmpty() - Truth.assertThat(bhum.hasNotifications()).isTrue() - } - - @Test - @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testHasNotifications_false() { - val bhum = createHeadsUpManager() - Truth.assertThat(bhum.mHeadsUpEntryMap).isEmpty() - Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isEmpty() - Truth.assertThat(bhum.hasNotifications()).isFalse() - } - - @Test - @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testGetHeadsUpEntryList_includesAvalancheEntryList() { - val bhum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val headsUpEntry = bhum.createHeadsUpEntry(notifEntry) - mAvalancheController!!.addToNext(headsUpEntry) {} - - Truth.assertThat(bhum.headsUpEntryList).contains(headsUpEntry) - } - - @Test - @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testGetHeadsUpEntry_returnsAvalancheEntry() { - val bhum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val headsUpEntry = bhum.createHeadsUpEntry(notifEntry) - mAvalancheController!!.addToNext(headsUpEntry) {} - - Truth.assertThat(bhum.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry) - } - - @Test - fun testShowNotification_addsEntry() { - val alm = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - alm.showNotification(entry) - - assertThat(alm.isHeadsUpEntry(entry.key)).isTrue() - assertThat(alm.hasNotifications()).isTrue() - assertThat(alm.getEntry(entry.key)).isEqualTo(entry) - } - - @Test - fun testShowNotification_autoDismisses() { - val alm = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - alm.showNotification(entry) - systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong()) - - assertThat(alm.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testRemoveNotification_removeDeferred() { - val alm = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - alm.showNotification(entry) - - val removedImmediately = - alm.removeNotification(entry.key, /* releaseImmediately= */ false, "removeDeferred") - assertThat(removedImmediately).isFalse() - assertThat(alm.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testRemoveNotification_forceRemove() { - val alm = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - alm.showNotification(entry) - - val removedImmediately = - alm.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove") - assertThat(removedImmediately).isTrue() - assertThat(alm.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testReleaseAllImmediately() { - val alm = createHeadsUpManager() - for (i in 0 until TEST_NUM_NOTIFICATIONS) { - val entry = HeadsUpManagerTestUtil.createEntry(i, mContext) - entry.row = mRow - alm.showNotification(entry) - } - - alm.releaseAllImmediately() - - assertThat(alm.allEntries.count()).isEqualTo(0) - } - - @Test - fun testCanRemoveImmediately_notShownLongEnough() { - val alm = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - alm.showNotification(entry) - - // The entry has just been added so we should not remove immediately. - assertThat(alm.canRemoveImmediately(entry.key)).isFalse() - } - - @Test - fun testHunRemovedLogging() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - val headsUpEntry = Mockito.mock(HeadsUpEntry::class.java) - Mockito.`when`(headsUpEntry.pinnedStatus) - .thenReturn(MutableStateFlow(PinnedStatus.NotPinned)) - headsUpEntry.mEntry = notifEntry - - hum.onEntryRemoved(headsUpEntry, "test") - - Mockito.verify(mLogger, Mockito.times(1)).logNotificationActuallyRemoved(eq(notifEntry)) - } - - @Test - fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - systemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong()) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testShowNotification_autoDismissesWithDefaultTimeout() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - systemClock.advanceTime( - (TEST_TOUCH_ACCEPTANCE_TIME + - (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) - .toLong() - ) - - assertThat(hum.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() { - val hum = createHeadsUpManager() - val entry = createStickyForSomeTimeEntry(/* id= */ 0) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - systemClock.advanceTime( - (TEST_TOUCH_ACCEPTANCE_TIME + - (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2) - .toLong() - ) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testShowNotification_sticky_neverAutoDismisses() { - val hum = createHeadsUpManager() - val entry = createStickyEntry(/* id= */ 0) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - systemClock.advanceTime( - (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong() - ) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testShowNotification_autoDismissesWithAccessibilityTimeout() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - useAccessibilityTimeout(true) - - hum.showNotification(entry) - systemClock.advanceTime( - (TEST_TOUCH_ACCEPTANCE_TIME + - (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) - .toLong() - ) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() { - val hum = createHeadsUpManager() - val entry = createStickyForSomeTimeEntry(/* id= */ 0) - useAccessibilityTimeout(true) - - hum.showNotification(entry) - systemClock.advanceTime( - (TEST_TOUCH_ACCEPTANCE_TIME + - (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) - .toLong() - ) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - } - - @Test - fun testRemoveNotification_beforeMinimumDisplayTime() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - - val removedImmediately = - hum.removeNotification( - entry.key, - /* releaseImmediately = */ false, - "beforeMinimumDisplayTime", - ) - assertThat(removedImmediately).isFalse() - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - - systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()) - - assertThat(hum.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testRemoveNotification_afterMinimumDisplayTime() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - useAccessibilityTimeout(false) - - hum.showNotification(entry) - systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()) - - assertThat(hum.isHeadsUpEntry(entry.key)).isTrue() - - val removedImmediately = - hum.removeNotification( - entry.key, - /* releaseImmediately = */ false, - "afterMinimumDisplayTime", - ) - assertThat(removedImmediately).isTrue() - assertThat(hum.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testRemoveNotification_releaseImmediately() { - val hum = createHeadsUpManager() - val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - hum.showNotification(entry) - - val removedImmediately = - hum.removeNotification( - entry.key, - /* releaseImmediately = */ true, - "afterMinimumDisplayTime", - ) - assertThat(removedImmediately).isTrue() - assertThat(hum.isHeadsUpEntry(entry.key)).isFalse() - } - - @Test - fun testIsSticky_rowPinnedAndExpanded_true() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - Mockito.`when`(mRow!!.isPinned).thenReturn(true) - notifEntry.row = mRow - - hum.showNotification(notifEntry) - - val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key) - headsUpEntry!!.setExpanded(true) - - assertThat(hum.isSticky(notifEntry.key)).isTrue() - } - - @Test - fun testIsSticky_remoteInputActive_true() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - hum.showNotification(notifEntry) - - val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key) - headsUpEntry!!.mRemoteInputActive = true - - assertThat(hum.isSticky(notifEntry.key)).isTrue() - } - - @Test - fun testIsSticky_hasFullScreenIntent_true() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) - - hum.showNotification(notifEntry) - - assertThat(hum.isSticky(notifEntry.key)).isTrue() - } - - @Test - fun testIsSticky_stickyForSomeTime_false() { - val hum = createHeadsUpManager() - val entry = createStickyForSomeTimeEntry(/* id= */ 0) - - hum.showNotification(entry) - - assertThat(hum.isSticky(entry.key)).isFalse() - } - - @Test - fun testIsSticky_false() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - hum.showNotification(notifEntry) - - val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key) - headsUpEntry!!.setExpanded(false) - headsUpEntry.mRemoteInputActive = false - - assertThat(hum.isSticky(notifEntry.key)).isFalse() - } - - @Test - fun testCompareTo_withNullEntries() { - val hum = createHeadsUpManager() - val alertEntry = NotificationEntryBuilder().setTag("alert").build() - - hum.showNotification(alertEntry) - - assertThat(hum.compare(alertEntry, null)).isLessThan(0) - assertThat(hum.compare(null, alertEntry)).isGreaterThan(0) - assertThat(hum.compare(null, null)).isEqualTo(0) - } - - @Test - fun testCompareTo_withNonAlertEntries() { - val hum = createHeadsUpManager() - - val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build() - val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build() - val alertEntry = NotificationEntryBuilder().setTag("alert").build() - hum.showNotification(alertEntry) - - assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0) - assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0) - assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0) - } - - @Test - fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() { - val hum = createHeadsUpManager() - - val ongoingCall = - hum.HeadsUpEntry( - NotificationEntryBuilder() - .setSbn( - HeadsUpManagerTestUtil.createSbn( - /* id = */ 0, - Notification.Builder(mContext, "") - .setCategory(Notification.CATEGORY_CALL) - .setOngoing(true), - ) - ) - .build() - ) - - val activeRemoteInput = - hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)) - activeRemoteInput.mRemoteInputActive = true - - assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0) - assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0) - } - - @Test - fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() { - val hum = createHeadsUpManager() - - val person = Person.Builder().setName("person").build() - val intent = Mockito.mock(PendingIntent::class.java) - val incomingCall = - hum.HeadsUpEntry( - NotificationEntryBuilder() - .setSbn( - HeadsUpManagerTestUtil.createSbn( - /* id = */ 0, - Notification.Builder(mContext, "") - .setStyle( - Notification.CallStyle.forIncomingCall(person, intent, intent) - ), - ) - ) - .build() - ) - - val activeRemoteInput = - hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)) - activeRemoteInput.mRemoteInputActive = true - - assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0) - assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0) - } - - @Test - @EnableFlags(NotificationThrottleHun.FLAG_NAME) - fun testPinEntry_logsPeek_throttleEnabled() { - val hum = createHeadsUpManager() - - // Needs full screen intent in order to be pinned - val entryToPin = - hum.HeadsUpEntry( - HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) - ) - - // Note: the standard way to show a notification would be calling showNotification rather - // than onAlertEntryAdded. However, in practice showNotification in effect adds - // the notification and then updates it; in order to not log twice, the entry needs - // to have a functional ExpandableNotificationRow that can keep track of whether it's - // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. - hum.onEntryAdded(entryToPin) - - assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(2) - assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId()) - .isEqualTo(mUiEventLoggerFake.eventId(0)) - assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id) - .isEqualTo(mUiEventLoggerFake.eventId(1)) - } - - @Test - @DisableFlags(NotificationThrottleHun.FLAG_NAME) - fun testPinEntry_logsPeek_throttleDisabled() { - val hum = createHeadsUpManager() - - // Needs full screen intent in order to be pinned - val entryToPin = - hum.HeadsUpEntry( - HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) - ) - - // Note: the standard way to show a notification would be calling showNotification rather - // than onAlertEntryAdded. However, in practice showNotification in effect adds - // the notification and then updates it; in order to not log twice, the entry needs - // to have a functional ExpandableNotificationRow that can keep track of whether it's - // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. - hum.onEntryAdded(entryToPin) - - assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1) - assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id) - .isEqualTo(mUiEventLoggerFake.eventId(0)) - } - - @Test - fun testSetUserActionMayIndirectlyRemove() { - val hum = createHeadsUpManager() - val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) - - hum.showNotification(notifEntry) - - assertThat(hum.canRemoveImmediately(notifEntry.key)).isFalse() - - hum.setUserActionMayIndirectlyRemove(notifEntry) - - assertThat(hum.canRemoveImmediately(notifEntry.key)).isTrue() - } - - companion object { - const val TEST_TOUCH_ACCEPTANCE_TIME: Int = 200 - const val TEST_A11Y_AUTO_DISMISS_TIME: Int = 1000 - const val TEST_EXTENSION_TIME = 500 - - const val TEST_MINIMUM_DISPLAY_TIME: Int = 400 - const val TEST_AUTO_DISMISS_TIME: Int = 600 - const val TEST_STICKY_AUTO_DISMISS_TIME: Int = 800 - - // Number of notifications to use in tests requiring multiple notifications - private const val TEST_NUM_NOTIFICATIONS = 4 - - init { - Truth.assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME) - Truth.assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME) - Truth.assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME) - } - - @get:Parameters(name = "{0}") - @JvmStatic - val flags: List<FlagsParameterization> - get() = FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME) - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt index a5fecb8d0e8d..8420c49755b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt @@ -15,25 +15,38 @@ */ package com.android.systemui.statusbar.notification.headsup +import android.app.Notification +import android.app.PendingIntent +import android.app.Person import android.os.Handler +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization +import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import android.view.accessibility.accessibilityManager import android.view.accessibility.accessibilityManagerWrapper import androidx.test.filters.SmallTest import com.android.internal.logging.uiEventLoggerFake +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.dump.dumpManager +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.useUnconfinedTestDispatcher -import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager +import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.phone.keyguardBypassController import com.android.systemui.statusbar.policy.configurationController @@ -41,12 +54,19 @@ import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.mockExecutorHandler import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.times +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -54,19 +74,26 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper -class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplOldTest(flags) { - - private val headsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer()) +class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() { + init { + mSetFlagsRule.setFlagsParameterization(flags) + } private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val testScope = kosmos.testScope private val groupManager = mock<GroupMembershipManager>() private val bgHandler = mock<Handler>() + private val headsUpManagerLogger = mock<HeadsUpManagerLogger>() val statusBarStateController = kosmos.sysuiStatusBarStateController + private val globalSettings = kosmos.fakeGlobalSettings + private val systemClock = kosmos.fakeSystemClock + private val executor = kosmos.fakeExecutor + private val uiEventLoggerFake = kosmos.uiEventLoggerFake private val javaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope) + private lateinit var testHelper: NotificationTestHelper private lateinit var avalancheController: AvalancheController private lateinit var underTest: HeadsUpManagerImpl @@ -90,12 +117,15 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO ) } + allowTestableLooperAsMainThread() + testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this)) + whenever(kosmos.keyguardBypassController.bypassEnabled).thenReturn(false) kosmos.visualStabilityProvider.isReorderingAllowed = true avalancheController = AvalancheController( kosmos.dumpManager, - kosmos.uiEventLoggerFake, + uiEventLoggerFake, headsUpManagerLogger, bgHandler, ) @@ -113,7 +143,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO systemClock, executor, kosmos.accessibilityManagerWrapper, - kosmos.uiEventLoggerFake, + uiEventLoggerFake, javaAdapter, kosmos.shadeInteractor, avalancheController, @@ -121,6 +151,220 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO } @Test + fun testHasNotifications_headsUpManagerMapNotEmpty_true() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + underTest.showNotification(entry) + + assertThat(underTest.mHeadsUpEntryMap).isNotEmpty() + assertThat(underTest.hasNotifications()).isTrue() + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testHasNotifications_avalancheMapNotEmpty_true() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + val headsUpEntry = underTest.createHeadsUpEntry(notifEntry) + avalancheController.addToNext(headsUpEntry) {} + + assertThat(avalancheController.getWaitingEntryList()).isNotEmpty() + assertThat(underTest.hasNotifications()).isTrue() + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testHasNotifications_false() { + assertThat(underTest.mHeadsUpEntryMap).isEmpty() + assertThat(avalancheController.getWaitingEntryList()).isEmpty() + assertThat(underTest.hasNotifications()).isFalse() + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testGetHeadsUpEntryList_includesAvalancheEntryList() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + val headsUpEntry = underTest.createHeadsUpEntry(notifEntry) + avalancheController.addToNext(headsUpEntry) {} + + assertThat(underTest.headsUpEntryList).contains(headsUpEntry) + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testGetHeadsUpEntry_returnsAvalancheEntry() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + val headsUpEntry = underTest.createHeadsUpEntry(notifEntry) + avalancheController.addToNext(headsUpEntry) {} + + assertThat(underTest.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry) + } + + @Test + fun testShowNotification_addsEntry() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + assertThat(underTest.hasNotifications()).isTrue() + assertThat(underTest.getEntry(entry.key)).isEqualTo(entry) + } + + @Test + fun testShowNotification_autoDismisses() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong()) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testRemoveNotification_removeDeferred() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately= */ false, + "removeDeferred", + ) + assertThat(removedImmediately).isFalse() + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testRemoveNotification_forceRemove() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + + val removedImmediately = + underTest.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove") + assertThat(removedImmediately).isTrue() + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testReleaseAllImmediately() { + for (i in 0 until 4) { + val entry = HeadsUpManagerTestUtil.createEntry(i, mContext) + entry.row = mock<ExpandableNotificationRow>() + underTest.showNotification(entry) + } + + underTest.releaseAllImmediately() + + assertThat(underTest.allEntries.count()).isEqualTo(0) + } + + @Test + fun testCanRemoveImmediately_notShownLongEnough() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + + // The entry has just been added so we should not remove immediately. + assertThat(underTest.canRemoveImmediately(entry.key)).isFalse() + } + + @Test + fun testHunRemovedLogging() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + val headsUpEntry = underTest.HeadsUpEntry(notifEntry) + headsUpEntry.setRowPinnedStatus(PinnedStatus.NotPinned) + + underTest.onEntryRemoved(headsUpEntry, "test") + + verify(headsUpManagerLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry)) + } + + @Test + fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + systemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong()) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testShowNotification_autoDismissesWithDefaultTimeout() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + systemClock.advanceTime( + (TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) + .toLong() + ) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testRemoveNotification_beforeMinimumDisplayTime() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately = */ false, + "beforeMinimumDisplayTime", + ) + assertThat(removedImmediately).isFalse() + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + + systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testRemoveNotification_afterMinimumDisplayTime() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong()) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately = */ false, + "afterMinimumDisplayTime", + ) + assertThat(removedImmediately).isTrue() + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test + fun testRemoveNotification_releaseImmediately() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(entry) + + val removedImmediately = + underTest.removeNotification( + entry.key, + /* releaseImmediately = */ true, + "afterMinimumDisplayTime", + ) + assertThat(removedImmediately).isTrue() + assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse() + } + + @Test fun testSnooze() { val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) underTest.showNotification(entry) @@ -160,7 +404,7 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO fun testCanRemoveImmediately_notTopEntry() { val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext) - laterEntry.row = mRow + laterEntry.row = mock<ExpandableNotificationRow>() underTest.showNotification(earlierEntry) underTest.showNotification(laterEntry) @@ -226,6 +470,122 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO } @Test + fun testShowNotification_sticky_neverAutoDismisses() { + val entry = createStickyEntry(id = 0) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + systemClock.advanceTime( + (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong() + ) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testShowNotification_autoDismissesWithAccessibilityTimeout() { + val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + useAccessibilityTimeout(true) + + underTest.showNotification(entry) + systemClock.advanceTime( + (TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) + .toLong() + ) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() { + val entry = createStickyForSomeTimeEntry(id = 0) + useAccessibilityTimeout(false) + + underTest.showNotification(entry) + systemClock.advanceTime( + (TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2) + .toLong() + ) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() { + val entry = createStickyForSomeTimeEntry(id = 0) + useAccessibilityTimeout(true) + + underTest.showNotification(entry) + systemClock.advanceTime( + (TEST_TOUCH_ACCEPTANCE_TIME + + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2) + .toLong() + ) + + assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue() + } + + @Test + fun testIsSticky_rowPinnedAndExpanded_true() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + val row = testHelper.createRow() + row.setPinnedStatus(PinnedStatus.PinnedBySystem) + notifEntry.row = row + + underTest.showNotification(notifEntry) + + val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.setExpanded(true) + + assertThat(underTest.isSticky(notifEntry.key)).isTrue() + } + + @Test + fun testIsSticky_remoteInputActive_true() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(notifEntry) + + val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.mRemoteInputActive = true + + assertThat(underTest.isSticky(notifEntry.key)).isTrue() + } + + @Test + fun testIsSticky_hasFullScreenIntent_true() { + val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) + + underTest.showNotification(notifEntry) + + assertThat(underTest.isSticky(notifEntry.key)).isTrue() + } + + @Test + fun testIsSticky_stickyForSomeTime_false() { + val entry = createStickyForSomeTimeEntry(id = 0) + + underTest.showNotification(entry) + + assertThat(underTest.isSticky(entry.key)).isFalse() + } + + @Test + fun testIsSticky_false() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(notifEntry) + + val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key) + headsUpEntry!!.setExpanded(false) + headsUpEntry.mRemoteInputActive = false + + assertThat(underTest.isSticky(notifEntry.key)).isFalse() + } + + @Test fun testShouldHeadsUpBecomePinned_noFSI_false() = kosmos.runTest { statusBarStateController.setState(StatusBarState.KEYGUARD) @@ -270,11 +630,13 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO } @Test + @BrokenWithSceneContainer(381869885) // because `ShadeTestUtil.setShadeExpansion(0f)` + // still causes `ShadeInteractor.isAnyExpanded` to emit `true`, when it should emit `false`. fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() = kosmos.runTest { // GIVEN - shadeTestUtil.setShadeExpansion(0f) - // TODO(b/381869885): Determine why we need both of these ShadeTestUtil calls. + // TODO(b/381869885): We should be able to use `ShadeTestUtil.setShadeExpansion(0f)` + // instead. shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false) val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) @@ -347,8 +709,183 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplO assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse() } + @Test + fun testCompareTo_withNullEntries() { + val alertEntry = NotificationEntryBuilder().setTag("alert").build() + + underTest.showNotification(alertEntry) + + assertThat(underTest.compare(alertEntry, null)).isLessThan(0) + assertThat(underTest.compare(null, alertEntry)).isGreaterThan(0) + assertThat(underTest.compare(null, null)).isEqualTo(0) + } + + @Test + fun testCompareTo_withNonAlertEntries() { + val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build() + val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build() + val alertEntry = NotificationEntryBuilder().setTag("alert").build() + underTest.showNotification(alertEntry) + + assertThat(underTest.compare(alertEntry, nonAlertEntry1)).isLessThan(0) + assertThat(underTest.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0) + assertThat(underTest.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0) + } + + @Test + fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() { + val ongoingCall = + underTest.HeadsUpEntry( + NotificationEntryBuilder() + .setSbn( + HeadsUpManagerTestUtil.createSbn( + /* id = */ 0, + Notification.Builder(mContext, "") + .setCategory(Notification.CATEGORY_CALL) + .setOngoing(true), + ) + ) + .build() + ) + + val activeRemoteInput = + underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)) + activeRemoteInput.mRemoteInputActive = true + + assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0) + assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0) + } + + @Test + fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() { + val person = Person.Builder().setName("person").build() + val intent = mock<PendingIntent>() + val incomingCall = + underTest.HeadsUpEntry( + NotificationEntryBuilder() + .setSbn( + HeadsUpManagerTestUtil.createSbn( + /* id = */ 0, + Notification.Builder(mContext, "") + .setStyle( + Notification.CallStyle.forIncomingCall(person, intent, intent) + ), + ) + ) + .build() + ) + + val activeRemoteInput = + underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)) + activeRemoteInput.mRemoteInputActive = true + + assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0) + assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0) + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + fun testPinEntry_logsPeek_throttleEnabled() { + // Needs full screen intent in order to be pinned + val entryToPin = + underTest.HeadsUpEntry( + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) + ) + + // Note: the standard way to show a notification would be calling showNotification rather + // than onAlertEntryAdded. However, in practice showNotification in effect adds + // the notification and then updates it; in order to not log twice, the entry needs + // to have a functional ExpandableNotificationRow that can keep track of whether it's + // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. + underTest.onEntryAdded(entryToPin) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2) + assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId()) + .isEqualTo(uiEventLoggerFake.eventId(0)) + assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id) + .isEqualTo(uiEventLoggerFake.eventId(1)) + } + + @Test + @DisableFlags(NotificationThrottleHun.FLAG_NAME) + fun testPinEntry_logsPeek_throttleDisabled() { + // Needs full screen intent in order to be pinned + val entryToPin = + underTest.HeadsUpEntry( + HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext) + ) + + // Note: the standard way to show a notification would be calling showNotification rather + // than onAlertEntryAdded. However, in practice showNotification in effect adds + // the notification and then updates it; in order to not log twice, the entry needs + // to have a functional ExpandableNotificationRow that can keep track of whether it's + // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit. + underTest.onEntryAdded(entryToPin) + + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id) + .isEqualTo(uiEventLoggerFake.eventId(0)) + } + + @Test + fun testSetUserActionMayIndirectlyRemove() { + val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext) + + underTest.showNotification(notifEntry) + + assertThat(underTest.canRemoveImmediately(notifEntry.key)).isFalse() + + underTest.setUserActionMayIndirectlyRemove(notifEntry) + + assertThat(underTest.canRemoveImmediately(notifEntry.key)).isTrue() + } + + private fun createStickyEntry(id: Int): NotificationEntry { + val notif = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setFullScreenIntent(mock<PendingIntent>(), /* highPriority= */ true) + .build() + return HeadsUpManagerTestUtil.createEntry(id, notif) + } + + private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry { + val notif = + Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true) + .build() + return HeadsUpManagerTestUtil.createEntry(id, notif) + } + + private fun useAccessibilityTimeout(use: Boolean) { + if (use) { + whenever(kosmos.accessibilityManager.getRecommendedTimeoutMillis(any(), any())) + .thenReturn(TEST_A11Y_AUTO_DISMISS_TIME) + } else { + doAnswer { it.getArgument(0) as Int } + .whenever(kosmos.accessibilityManager) + .getRecommendedTimeoutMillis(any(), any()) + } + } + companion object { + const val TEST_TOUCH_ACCEPTANCE_TIME = 200 + const val TEST_A11Y_AUTO_DISMISS_TIME = 1000 + const val TEST_EXTENSION_TIME = 500 + + const val TEST_MINIMUM_DISPLAY_TIME = 400 + const val TEST_AUTO_DISMISS_TIME = 600 + const val TEST_STICKY_AUTO_DISMISS_TIME = 800 + + init { + assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME) + assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME) + assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME) + } + @get:Parameters(name = "{0}") + @JvmStatic val flags: List<FlagsParameterization> get() = buildList { addAll( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index f76f1ce48a5c..7f139bd69a37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -30,6 +30,8 @@ import android.content.ComponentName; import android.os.PowerManager; import android.os.UserHandle; import android.os.Vibrator; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.view.HapticFeedbackConstants; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -45,6 +47,8 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QSPanelController; +import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; @@ -54,15 +58,14 @@ import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; +import com.android.systemui.shade.shared.flag.DualShade; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import dagger.Lazy; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,6 +75,8 @@ import org.mockito.stubbing.Answer; import java.util.Optional; +import dagger.Lazy; + @SmallTest @RunWith(AndroidJUnit4.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @@ -105,6 +110,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private ActivityStarter mActivityStarter; @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory; @Mock private KeyguardInteractor mKeyguardInteractor; + @Mock private QSPanelController mQSPanelController; CentralSurfacesCommandQueueCallbacks mSbcqCallbacks; @@ -150,6 +156,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt())) .thenAnswer((Answer<Integer>) invocation -> invocation.getArgument(0)); + when(mCentralSurfaces.getQSPanelController()).thenReturn(mQSPanelController); } @Test @@ -230,4 +237,45 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { verify(mQSHost).addTile(c, false); } + + @Test + @DisableFlags(value = {QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME}) + public void clickQsTile_flagsDisabled_callsQSPanelController() { + ComponentName c = new ComponentName("testpkg", "testcls"); + + mSbcqCallbacks.clickTile(c); + verify(mQSPanelController).clickTile(c); + } + + @Test + @DisableFlags(DualShade.FLAG_NAME) + @EnableFlags(QSComposeFragment.FLAG_NAME) + public void clickQsTile_onlyQSComposeFlag_callsQSHost() { + ComponentName c = new ComponentName("testpkg", "testcls"); + + mSbcqCallbacks.clickTile(c); + verify(mQSPanelController, never()).clickTile(c); + verify(mQSHost).clickTile(c); + } + + @Test + @EnableFlags(DualShade.FLAG_NAME) + @DisableFlags(QSComposeFragment.FLAG_NAME) + public void clickQsTile_onlyDualShadeFlag_callsQSHost() { + ComponentName c = new ComponentName("testpkg", "testcls"); + + mSbcqCallbacks.clickTile(c); + verify(mQSPanelController, never()).clickTile(c); + verify(mQSHost).clickTile(c); + } + + @Test + @EnableFlags(value = {QSComposeFragment.FLAG_NAME, DualShade.FLAG_NAME}) + public void clickQsTile_qsComposeAndDualShadeFlags_callsQSHost() { + ComponentName c = new ComponentName("testpkg", "testcls"); + + mSbcqCallbacks.clickTile(c); + verify(mQSPanelController, never()).clickTile(c); + verify(mQSHost).clickTile(c); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index c0a206afe64b..9ad23154c334 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -49,11 +49,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() private val testScope = TestScope(dispatcher) - private val iconsInteractor = - FakeMobileIconsInteractor( - FakeMobileMappingsProxy(), - mock(), - ) + private val iconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) private val repo = FakeDeviceBasedSatelliteRepository() private val connectivityRepository = FakeConnectivityRepository() @@ -515,7 +511,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { // GIVEN, 2 connection val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) - val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) // WHEN all connections are NOT OOS. i1.isInService.value = true @@ -547,7 +543,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { // GIVEN a condition that should return true (all conections OOS) val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) - val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) i1.isInService.value = true i2.isInService.value = true @@ -579,4 +575,40 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { // THEN the interactor returns true due to the wifi network being active assertThat(latest).isTrue() } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun isAnyConnectionNtn_trueWhenAnyNtn() = + testScope.runTest { + val latest by collectLastValue(underTest.isAnyConnectionNtn) + + // GIVEN, 2 connection + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + + // WHEN at least one connection is using ntn + i1.isNonTerrestrial.value = true + i2.isNonTerrestrial.value = false + + // THEN the value is propagated to this interactor + assertThat(latest).isTrue() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun isAnyConnectionNtn_falseWhenNoNtn() = + testScope.runTest { + val latest by collectLastValue(underTest.isAnyConnectionNtn) + + // GIVEN, 2 connection + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + + // WHEN at no connection is using ntn + i1.isNonTerrestrial.value = false + i2.isNonTerrestrial.value = false + + // THEN the value is propagated to this interactor + assertThat(latest).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index 509aa7ad67fd..fe5b56a4e66d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -327,10 +327,11 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { // GIVEN satellite is allowed repo.isSatelliteAllowedForCurrentLocation.value = true - // GIVEN all icons are OOS + // GIVEN all icons are OOS and not ntn val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) i1.isInService.value = false i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false // GIVEN apm is disabled airplaneModeRepository.setIsAirplaneMode(false) @@ -344,6 +345,29 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { } @Test + fun icon_nullWhenConnected_mobileNtnConnectionExists() = + testScope.runTest { + val latest by collectLastValue(underTest.icon) + + // GIVEN satellite is allowed + repo.isSatelliteAllowedForCurrentLocation.value = true + + // GIVEN ntn connection exists + val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1) + i1.isNonTerrestrial.value = true + + // GIVEN apm is disabled + airplaneModeRepository.setIsAirplaneMode(false) + + // GIVEN satellite reports that it is Connected + repo.connectionState.value = SatelliteConnectionState.On + + // THEN icon is null because despite being connected, the mobile stack is reporting a + // nonTerrestrial network, and therefore will have its own icon + assertThat(latest).isNull() + } + + @Test fun icon_satelliteIsProvisioned() = testScope.runTest { val latest by collectLastValue(underTest.icon) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt index 9888574071e6..a2ca12c13a3e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt @@ -30,6 +30,7 @@ class FakeHomeStatusBarViewBinder : HomeStatusBarViewBinder { var listener: StatusBarVisibilityChangeListener? = null override fun bind( + displayId: Int, view: View, viewModel: HomeStatusBarViewModel, systemEventChipAnimateIn: ((View) -> Unit)?, diff --git a/packages/SystemUI/res/drawable/ic_screensaver_auto.xml b/packages/SystemUI/res/drawable/ic_screensaver_auto.xml new file mode 100644 index 000000000000..7cccff624c6e --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_screensaver_auto.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L160,720Q160,720 160,720Q160,720 160,720L160,240Q160,240 160,240Q160,240 160,240L160,240Q160,240 160,240Q160,240 160,240Q160,240 160,240Q160,240 160,240ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L440,160Q440,179 440,199Q440,219 440,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L800,720Q800,720 800,720Q800,720 800,720L800,480Q823,480 843,480Q863,480 880,480L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM700,480Q700,388 636,324Q572,260 480,260Q572,260 636,196Q700,132 700,40Q700,132 764,196Q828,260 920,260Q828,260 764,324Q700,388 700,480Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 2b40df47667a..5922a7dcdcf0 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -149,7 +149,8 @@ android:clickable="false" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="start|center_vertical"> + android:minHeight="72dp" + android:gravity="center_vertical"> <TextView android:id="@+id/mobile_title" android:maxLines="1" diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml index c1852b106544..9ac456c17084 100644 --- a/packages/SystemUI/res/layout/volume_dialog_slider.xml +++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml @@ -14,15 +14,21 @@ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="@dimen/volume_dialog_slider_width" - android:layout_height="@dimen/volume_dialog_slider_height"> + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> <com.google.android.material.slider.Slider - style="@style/SystemUI.Material3.Slider.Volume" android:id="@+id/volume_dialog_slider" - android:layout_width="@dimen/volume_dialog_slider_height" - android:layout_height="match_parent" + style="@style/SystemUI.Material3.Slider.Volume" + android:layout_width="@dimen/volume_dialog_slider_width" + android:layout_height="@dimen/volume_dialog_slider_height" android:layout_gravity="center" - android:rotation="270" - android:theme="@style/Theme.Material3.Light" /> -</FrameLayout> + android:theme="@style/Theme.Material3.Light" + android:orientation="vertical" + app:thumbHeight="52dp" + app:trackCornerSize="12dp" + app:trackHeight="40dp" + app:trackStopIndicatorSize="6dp" + app:trackInsideCornerSize="2dp" /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 478050b0ed85..df7adc019a72 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -237,6 +237,9 @@ <dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen> <!-- Height of a small notification in the status bar (2025 redesign version) --> + <dimen name="notification_2025_header_height">@*android:dimen/notification_2025_header_height</dimen> + + <!-- Height of a small notification in the status bar (2025 redesign version) --> <dimen name="notification_2025_min_height">@*android:dimen/notification_2025_min_height</dimen> <!-- Height of a small notification in the status bar--> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index fac2c4a5a9c2..49ac145d33cc 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1342,6 +1342,8 @@ <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add the \"Widgets\" shortcut, make sure \"Show widgets on lock screen\" is enabled in settings.</string> <!-- Label for a button used to open Settings in order to enable showing widgets on the lock screen. [CHAR LIMIT=NONE] --> <string name="glanceable_hub_lockscreen_affordance_action_button_label">Settings</string> + <!-- Content description for a "show screensaver" button on glanceable hub. [CHAR LIMIT=NONE] --> + <string name="accessibility_glanceable_hub_to_dream_button">Show screensaver button</string> <!-- Related to user switcher --><skip/> @@ -1509,6 +1511,9 @@ <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] --> <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string> + <!-- Content description for accessibility: Tapping this button will open notifications settings [CHAR LIMIT=NONE] --> + <string name="accessibility_notification_section_header_open_settings">Open notifications settings</string> + <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] --> <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string> diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags index 08236b7e280a..ca5424bc0c52 100644 --- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags +++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.systemui; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index a46b236d46fb..981732278acd 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -46,6 +46,8 @@ import com.android.systemui.process.ProcessWrapper; import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.ConfigurationForwarder; import com.android.systemui.util.NotificationChannels; +import com.android.wm.shell.dagger.HasWMComponent; +import com.android.wm.shell.dagger.WMComponent; import java.lang.reflect.InvocationTargetException; import java.util.ArrayDeque; @@ -62,7 +64,7 @@ import javax.inject.Provider; * Application class for SystemUI. */ public class SystemUIApplication extends Application implements - SystemUIAppComponentFactoryBase.ContextInitializer { + SystemUIAppComponentFactoryBase.ContextInitializer, HasWMComponent { public static final String TAG = "SystemUIService"; private static final boolean DEBUG = false; @@ -490,4 +492,10 @@ public class SystemUIApplication extends Application implements n.addExtras(extras); } + + @NonNull + @Override + public WMComponent getWMComponent() { + return mInitializer.getWMComponent(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index 5c75a49818a6..f530522fb707 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -24,9 +24,9 @@ import android.util.Log; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; -import com.android.systemui.dagger.WMComponent; import com.android.systemui.res.R; import com.android.systemui.util.InitializationChecker; +import com.android.wm.shell.dagger.WMComponent; import com.android.wm.shell.dagger.WMShellConcurrencyModule; import com.android.wm.shell.keyguard.KeyguardTransitions; import com.android.wm.shell.shared.ShellTransitions; diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java index d4e74d3bb906..9e1b09cf7891 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java @@ -162,11 +162,12 @@ public class AmbientStatusBarView extends ConstraintLayout { void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { View icon = mStatusIcons.get(iconType); - if (icon == null) { - return; - } + if (icon == null) return; + if (show && contentDescription != null) { icon.setContentDescription(contentDescription); + icon.setFocusable(true); + icon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } icon.setVisibility(show ? View.VISIBLE : View.GONE); mSystemStatusViewGroup.setVisibility(areAnyStatusIconsVisible() ? View.VISIBLE : View.GONE); @@ -174,9 +175,12 @@ public class AmbientStatusBarView extends ConstraintLayout { void setExtraStatusBarItemViews(List<View> views) { removeAllExtraStatusBarItemViews(); - views.forEach(view -> mExtraSystemStatusViewGroup.addView(view)); + views.forEach(view -> { + view.setFocusable(true); + view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + mExtraSystemStatusViewGroup.addView(view); + }); } - private View fetchStatusIconForResId(int resId) { final View statusIcon = findViewById(resId); return Objects.requireNonNull(statusIcon); diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index cbdb8827e39c..5cf4b4faed78 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -242,6 +242,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon op.getUid(), op.getPackageName(), /* attributionTag= */ attributedOpEntry.getKey(), + Context.DEVICE_ID_DEFAULT, /* active= */ true, // AppOpsManager doesn't have a way to fetch attribution flags or // chain ID given an op entry, so default them to none. @@ -440,14 +441,14 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * Required to override, delegate to other. Should not be called. */ public void onOpActiveChanged(String op, int uid, String packageName, boolean active) { - onOpActiveChanged(op, uid, packageName, null, active, + onOpActiveChanged(op, uid, packageName, null, Context.DEVICE_ID_DEFAULT, active, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE); } // Get active app ops, and check if their attributions are trusted @Override public void onOpActiveChanged(String op, int uid, String packageName, String attributionTag, - boolean active, int attributionFlags, int attributionChainId) { + int virtualDeviceId, boolean active, int attributionFlags, int attributionChainId) { int code = AppOpsManager.strOpToOp(op); if (DEBUG) { Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s,%d,%d)", code, uid, packageName, diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt index b2d02edf3c45..a31e61f67e47 100644 --- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt @@ -107,6 +107,7 @@ constructor( activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true) activityOptions.rotationAnimationHint = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS + intent.collectExtraIntentKeys() try { activityTaskManager.startActivityAsUser( null, diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt index 074b64e0fab0..69f4f6d30f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt @@ -74,3 +74,12 @@ constructor( /** Returns `true` if the tap gesture should be rejected */ fun isFalseTap(@Penalty penalty: Int): Boolean = manager.isFalseTap(penalty) } + +inline fun FalsingInteractor.runIfNotFalseTap( + penalty: Int = FalsingManager.LOW_PENALTY, + action: () -> Unit, +) { + if (!isFalseTap(penalty)) { + action() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index d648b9c6442b..5644e6b3b9bf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -23,7 +23,6 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.internal.logging.UiEventLogger import com.android.systemui.CoreStartable -import com.android.systemui.Flags.communalHubOnMobile import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -218,7 +217,8 @@ constructor( newScene = CommunalScenes.Blank, loggingReason = "hub timeout", transitionKey = - if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade + if (communalSettingsInteractor.isV2FlagEnabled()) + CommunalTransitionKeys.SimpleFade else null, ) uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index f9b30c6c2ba1..ea428698e476 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -27,6 +27,7 @@ import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository @@ -535,7 +536,9 @@ constructor( // Order by creation time descending. ongoingContent.sortByDescending { it.createdTimestampMillis } // Resize the items. - ongoingContent.resizeItems() + if (!communalResponsiveGrid()) { + ongoingContent.resizeItems() + } // Return the sorted and resized items. ongoingContent diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 0cdbad40b2d1..862b05bc9b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -63,24 +63,41 @@ constructor( .logDiffsForTable( tableLogBuffer = tableLogBuffer, columnPrefix = "disabledReason", - initialValue = CommunalEnabledState() + initialValue = CommunalEnabledState(), ) .map { model -> model.enabled } // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) /** - * Returns true if both the communal trunk-stable flag and resource flag are enabled. + * Returns true if any glanceable hub functionality should be enabled via configs and flags. * - * The trunk-stable flag is controlled by server rollout and is on all devices. The resource - * flag is enabled via resource overlay only on products we want the hub to be present on. + * This should be used for preventing basic glanceable hub functionality from running on devices + * that don't need it. * * If this is false, then the hub is definitely not available on the device. If this is true, * refer to [isCommunalEnabled] which takes into account other factors that can change at * runtime. + * + * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android + * config boolean. Otherwise, checks the old config_communalServiceEnabled config and + * communal_hub flag. */ fun isCommunalFlagEnabled(): Boolean = repository.getFlagEnabled() + /** + * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag + * are enabled. + * + * This should be used to flag off new glanceable hub or dream behavior that should launch + * together with the new hub experience that brings the hub to mobile. + * + * The trunk-stable flag is controlled by server rollout and is on all devices. The Android + * config flag is enabled via resource overlay only on products we want the hub to be present + * on. + */ + fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled() + /** The type of background to use for the hub. Used to experiment with different backgrounds */ val communalBackground: Flow<CommunalBackgroundType> = userInteractor.selectedUserInfo @@ -120,6 +137,6 @@ constructor( .stateIn( scope = bgScope, started = SharingStarted.WhileSubscribed(), - initialValue = null + initialValue = null, ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index da613f58dce8..c0456d5bb46e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -110,6 +110,11 @@ sealed interface CommunalContentModel { get() = fixedHalfOrResponsiveSize() } + /** An empty spacer to reserve space in the grid. */ + data class Spacer(override val size: CommunalContentSize) : CommunalContentModel { + override val key: String = KEY.spacer() + } + /** A CTA tile in the glanceable hub view mode which can be dismissed. */ class CtaTileInViewMode : CommunalContentModel { override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY @@ -171,6 +176,10 @@ sealed interface CommunalContentModel { fun umo(): String { return "umo" } + + fun spacer(): String { + return "spacer_${UUID.randomUUID()}" + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt new file mode 100644 index 000000000000..7d5b196dfaa8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModel.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.annotation.SuppressLint +import android.app.DreamManager +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.util.kotlin.isDevicePluggedIn +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class CommunalToDreamButtonViewModel +@AssistedInject +constructor( + @Background private val backgroundContext: CoroutineContext, + batteryController: BatteryController, + private val dreamManager: DreamManager, +) : ExclusiveActivatable() { + + private val _requests = Channel<Unit>(Channel.BUFFERED) + + /** Whether we should show a button on hub to switch to dream. */ + @SuppressLint("MissingPermission") + val shouldShowDreamButtonOnHub = + batteryController + .isDevicePluggedIn() + .distinctUntilChanged() + .map { isPluggedIn -> isPluggedIn && dreamManager.canStartDreaming(true) } + .flowOn(backgroundContext) + + /** Handle a tap on the "show dream" button. */ + fun onShowDreamButtonTap() { + _requests.trySend(Unit) + } + + @SuppressLint("MissingPermission") + override suspend fun onActivated(): Nothing = coroutineScope { + launch { + _requests.receiveAsFlow().collectLatest { + withContext(backgroundContext) { dreamManager.startDream() } + } + } + + awaitCancellation() + } + + @AssistedFactory + interface Factory { + fun create(): CommunalToDreamButtonViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 9cd6465266d4..eb7420f76f59 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -90,7 +90,7 @@ constructor( private val keyguardIndicationController: KeyguardIndicationController, communalSceneInteractor: CommunalSceneInteractor, private val communalInteractor: CommunalInteractor, - communalSettingsInteractor: CommunalSettingsInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, @@ -372,6 +372,9 @@ constructor( val communalBackground: Flow<CommunalBackgroundType> = communalSettingsInteractor.communalBackground + /** See [CommunalSettingsInteractor.isV2FlagEnabled] */ + fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled() + companion object { const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L } diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/ResizeUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/util/ResizeUtils.kt new file mode 100644 index 000000000000..36cc1c051336 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/ResizeUtils.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.util + +import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalContentSize + +object ResizeUtils { + /** + * Resizes ongoing items such that we don't mix regular content with ongoing content. + * + * NOTE: This is *NOT* a pure function, as it modifies items in the input list. + * + * Assumptions: + * 1. Ongoing content is always at the start of the list. + * 2. The maximum size of ongoing content is 2 rows. + */ + fun resizeOngoingItems( + list: List<CommunalContentModel>, + numRows: Int, + ): List<CommunalContentModel> { + val finalizedList = mutableListOf<CommunalContentModel>() + val numOngoing = list.count { it is CommunalContentModel.Ongoing } + // Calculate the number of extra rows we have if each ongoing item were to take up a single + // row. This is the number of rows we have to distribute across items. + var extraRows = + if (numOngoing % numRows == 0) { + 0 + } else { + numRows - (numOngoing % numRows) + } + var remainingRows = numRows + + for (item in list) { + if (item is CommunalContentModel.Ongoing) { + if (remainingRows == 0) { + // Start a new column. + remainingRows = numRows + } + val newSize = if (extraRows > 0 && remainingRows > 1) 2 else 1 + item.size = CommunalContentSize.Responsive(newSize) + finalizedList.add(item) + extraRows -= (newSize - 1) + remainingRows -= newSize + } else { + if (numOngoing > 0 && remainingRows > 0) { + finalizedList.add( + CommunalContentModel.Spacer(CommunalContentSize.Responsive(remainingRows)) + ) + } + remainingRows = -1 + finalizedList.add(item) + } + } + return finalizedList + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java index e78ce3bbb0d1..f804c2e80ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java @@ -24,6 +24,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.process.ProcessWrapper; import com.android.systemui.util.InitializationChecker; +import com.android.wm.shell.dagger.WMComponent; import dagger.BindsInstance; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt index e76fd47c74de..c425bee74b2b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt @@ -25,7 +25,7 @@ import android.os.PatternMatcher import android.os.RemoteException import android.service.dreams.IDreamManager import android.util.Log -import com.android.systemui.Flags +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.qualifiers.SystemUser import com.android.systemui.dreams.dagger.DreamModule import com.android.systemui.log.LogBuffer @@ -48,6 +48,7 @@ constructor( @SystemUser monitor: Monitor, private val packageManager: PackageManager, private val dreamManager: IDreamManager, + private val communalSettingsInteractor: CommunalSettingsInteractor, @DreamLog private val logBuffer: LogBuffer, ) : ConditionalCoreStartable(monitor) { private var currentRegisteredState = false @@ -90,7 +91,7 @@ constructor( } if ( - Flags.communalHubOnMobile() && + communalSettingsInteractor.isV2FlagEnabled() && packageManager.getComponentEnabledSetting(overlayServiceComponent) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED ) { @@ -111,7 +112,7 @@ constructor( } // Enable for hub on mobile - if (Flags.communalHubOnMobile()) { + if (communalSettingsInteractor.isV2FlagEnabled()) { // Not available on TV or auto if ( packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) || diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index aee3a457e18a..571b37f43fd4 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -18,7 +18,6 @@ package com.android.systemui.dreams; import static android.service.dreams.Flags.dreamWakeRedirect; -import static com.android.systemui.Flags.communalHubOnMobile; import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE; import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER; @@ -60,6 +59,7 @@ import com.android.systemui.ambient.touch.TouchMonitor; import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent; import com.android.systemui.ambient.touch.scrim.ScrimManager; import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor; import com.android.systemui.communal.shared.log.CommunalUiEvent; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.communal.shared.model.CommunalTransitionKeys; @@ -171,6 +171,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final SceneInteractor mSceneInteractor; private final CommunalInteractor mCommunalInteractor; + private final CommunalSettingsInteractor mCommunalSettingsInteractor; private boolean mCommunalAvailable; @@ -383,6 +384,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ KeyguardUpdateMonitor keyguardUpdateMonitor, ScrimManager scrimManager, CommunalInteractor communalInteractor, + CommunalSettingsInteractor communalSettingsInteractor, SceneInteractor sceneInteractor, SystemDialogsCloser systemDialogsCloser, UiEventLogger uiEventLogger, @@ -411,6 +413,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mDreamOverlayCallbackController = dreamOverlayCallbackController; mWindowTitle = windowTitle; mCommunalInteractor = communalInteractor; + mCommunalSettingsInteractor = communalSettingsInteractor; mSceneInteractor = sceneInteractor; mSystemDialogsCloser = systemDialogsCloser; mGestureInteractor = gestureInteractor; @@ -488,7 +491,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ final ArrayList<TouchHandler> touchHandlers = new ArrayList<>( List.of(dreamComplicationComponent.getHideComplicationTouchHandler())); - if (!communalHubOnMobile()) { + if (!mCommunalSettingsInteractor.isV2FlagEnabled()) { // Do not add the communal touch handler for glanceable hub v2 since there is no dream // to hub swipe gesture. touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler()); @@ -575,7 +578,8 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } else { mCommunalInteractor.changeScene(CommunalScenes.Communal, "dream wake requested", - communalHubOnMobile() ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null); + mCommunalSettingsInteractor.isV2FlagEnabled() + ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null); } } diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt index 7242770e72e5..e2646353bc19 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt @@ -21,6 +21,9 @@ import com.android.systemui.CoreStartable import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.contextualeducation.GestureType import com.android.systemui.contextualeducation.GestureType.ALL_APPS +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.ContextualEducationMetricsLogger @@ -37,6 +40,7 @@ import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.time.Clock +import java.time.Instant import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.days @@ -48,6 +52,7 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.merge @@ -71,6 +76,8 @@ constructor( const val TAG = "KeyboardTouchpadEduInteractor" const val MAX_SIGNAL_COUNT: Int = 2 const val MAX_EDUCATION_SHOW_COUNT: Int = 2 + const val MAX_TOAST_PER_USAGE_SESSION: Int = 2 + val usageSessionDuration = getDurationForConfig("persist.contextual_edu.usage_session_sec", 3.days) val minIntervalBetweenEdu = @@ -110,6 +117,16 @@ constructor( awaitClose { overviewProxyService.removeCallback(listener) } } + private val gestureModelMap: Flow<Map<GestureType, GestureEduModel>> = + combine( + contextualEducationInteractor.backGestureModelFlow, + contextualEducationInteractor.homeGestureModelFlow, + contextualEducationInteractor.overviewGestureModelFlow, + contextualEducationInteractor.allAppsGestureModelFlow, + ) { back, home, overview, allApps -> + mapOf(BACK to back, HOME to home, OVERVIEW to overview, ALL_APPS to allApps) + } + @OptIn(ExperimentalCoroutinesApi::class) override fun start() { backgroundScope.launch { @@ -211,7 +228,11 @@ constructor( private suspend fun incrementSignalCount(gestureType: GestureType) { val targetDevice = getTargetDevice(gestureType) - if (isTargetDeviceConnected(targetDevice) && hasInitialDelayElapsed(targetDevice)) { + if ( + isTargetDeviceConnected(targetDevice) && + hasInitialDelayElapsed(targetDevice) && + isMinIntervalForToastEduElapsed(gestureType) + ) { contextualEducationInteractor.incrementSignalCount(gestureType) } } @@ -223,6 +244,28 @@ constructor( } } + private suspend fun isMinIntervalForToastEduElapsed(gestureType: GestureType): Boolean { + val gestureModelMap = gestureModelMap.first() + // Only perform checking if the next edu is toast (i.e. no education is shown yet) + if (gestureModelMap[gestureType]?.educationShownCount != 0) { + return true + } + + val wasLastEduToast = { gesture: GestureEduModel -> gesture.educationShownCount == 1 } + val toastEduTimesInCurrentSession: List<Instant> = + gestureModelMap.values + .filter { wasLastEduToast(it) } + .mapNotNull { it.lastEducationTime } + .filter { it >= clock.instant().minusSeconds(usageSessionDuration.inWholeSeconds) } + + return if (toastEduTimesInCurrentSession.size >= MAX_TOAST_PER_USAGE_SESSION) { + val lastToastTime: Instant? = toastEduTimesInCurrentSession.maxOrNull() + clock.instant().isAfter(lastToastTime?.plusSeconds(usageSessionDuration.inWholeSeconds)) + } else { + true + } + } + /** * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt index 8c393e27da59..3020e5dedd17 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/model/InternalShortcutModels.kt @@ -21,6 +21,7 @@ import android.hardware.input.InputGestureData import android.view.KeyboardShortcutGroup import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType /** * Internal Keyboard Shortcut models to use with [ShortcutCategoriesUtils.fetchShortcutCategory] @@ -55,3 +56,8 @@ data class InternalKeyboardShortcutInfo( val icon: Icon? = null, val isCustomShortcut: Boolean = false, ) + +data class InternalGroupsSource( + val groups: List<InternalKeyboardShortcutGroup>, + val type: ShortcutCategoryType, +)
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt index 4af378646db3..8afec04a621c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt @@ -16,10 +16,8 @@ package com.android.systemui.keyboard.shortcut.data.repository -import android.content.Context import android.hardware.input.InputGestureData import android.hardware.input.InputGestureData.Builder -import android.hardware.input.InputGestureData.KeyTrigger import android.hardware.input.InputGestureData.createKeyTrigger import android.hardware.input.InputManager import android.hardware.input.KeyGestureEvent.KeyGestureType @@ -30,11 +28,8 @@ import com.android.systemui.Flags.shortcutHelperKeyGlyph import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult -import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup -import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey @@ -57,8 +52,7 @@ constructor( @Background private val backgroundScope: CoroutineScope, @Background private val bgCoroutineContext: CoroutineContext, private val shortcutCategoriesUtils: ShortcutCategoriesUtils, - private val context: Context, - private val inputGestureMaps: InputGestureMaps, + private val inputGestureDataAdapter: InputGestureDataAdapter, private val customInputGesturesRepository: CustomInputGesturesRepository, private val inputManager: InputManager ) : ShortcutCategoriesRepository { @@ -116,7 +110,7 @@ constructor( if (inputDevice == null) { emptyList() } else { - val sources = toInternalGroupSources(inputGestures) + val sources = inputGestureDataAdapter.toInternalGroupSources(inputGestures) val supportedKeyCodes = shortcutCategoriesUtils.fetchSupportedKeyCodes( inputDevice.id, @@ -216,7 +210,8 @@ constructor( return null } - return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] + return inputGestureDataAdapter + .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label) } @KeyGestureType @@ -232,7 +227,8 @@ constructor( return null } - return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label] + return inputGestureDataAdapter + .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label) } private fun Builder.addTriggerFromSelectedKeyCombination(): Builder { @@ -261,70 +257,6 @@ constructor( return _shortcutBeingCustomized.value } - private fun toInternalGroupSources( - inputGestures: List<InputGestureData> - ): List<InternalGroupsSource> { - val ungroupedInternalGroupSources = - inputGestures.mapNotNull { gestureData -> - val keyTrigger = gestureData.trigger as KeyTrigger - val keyGestureType = gestureData.action.keyGestureType() - fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel -> - toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let { - internalKeyboardShortcutInfo -> - val group = - InternalKeyboardShortcutGroup( - label = groupLabel, - items = listOf(internalKeyboardShortcutInfo), - ) - - fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let { - InternalGroupsSource(groups = listOf(group), type = it) - } - } - } - } - - return ungroupedInternalGroupSources - } - - private fun toInternalKeyboardShortcutInfo( - keyGestureType: Int, - keyTrigger: KeyTrigger, - ): InternalKeyboardShortcutInfo? { - fetchShortcutInfoLabelByGestureType(keyGestureType)?.let { - return InternalKeyboardShortcutInfo( - label = it, - keycode = keyTrigger.keycode, - modifiers = keyTrigger.modifierState, - isCustomShortcut = true, - ) - } - return null - } - - private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? { - inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let { - return context.getString(it) - } ?: return null - } - - private fun fetchShortcutInfoLabelByGestureType(@KeyGestureType keyGestureType: Int): String? { - inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let { - return context.getString(it) - } ?: return null - } - - private fun fetchShortcutCategoryTypeByGestureType( - @KeyGestureType keyGestureType: Int - ): ShortcutCategoryType? { - return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType] - } - - private data class InternalGroupsSource( - val groups: List<InternalKeyboardShortcutGroup>, - val type: ShortcutCategoryType, - ) - private companion object { private const val TAG = "CustomShortcutCategoriesRepository" } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt new file mode 100644 index 000000000000..df7101e21cce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureDataAdapter.kt @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.annotation.SuppressLint +import android.app.role.RoleManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_MAIN +import android.content.Intent.CATEGORY_LAUNCHER +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY +import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE +import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE +import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES +import android.content.pm.PackageManager.NameNotFoundException +import android.graphics.drawable.Icon +import android.hardware.input.AppLaunchData +import android.hardware.input.AppLaunchData.CategoryData +import android.hardware.input.AppLaunchData.ComponentData +import android.hardware.input.AppLaunchData.RoleData +import android.hardware.input.InputGestureData +import android.hardware.input.InputGestureData.KeyTrigger +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION +import android.hardware.input.KeyGestureEvent.KeyGestureType +import android.util.Log +import com.android.internal.app.ResolverActivity +import com.android.systemui.keyboard.shortcut.data.model.InternalGroupsSource +import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutGroup +import com.android.systemui.keyboard.shortcut.data.model.InternalKeyboardShortcutInfo +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.res.R +import com.android.systemui.settings.UserTracker +import javax.inject.Inject + + +class InputGestureDataAdapter +@Inject +constructor( + private val userTracker: UserTracker, + private val inputGestureMaps: InputGestureMaps, + private val context: Context +) { + private val userContext: Context + get() = userTracker.createCurrentUserContext(userTracker.userContext) + + fun toInternalGroupSources( + inputGestures: List<InputGestureData> + ): List<InternalGroupsSource> { + val ungroupedInternalGroupSources = + inputGestures.mapNotNull { gestureData -> + val keyTrigger = gestureData.trigger as KeyTrigger + val keyGestureType = gestureData.action.keyGestureType() + val appLaunchData: AppLaunchData? = gestureData.action.appLaunchData() + fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel -> + toInternalKeyboardShortcutInfo( + keyGestureType, + keyTrigger, + appLaunchData + )?.let { internalKeyboardShortcutInfo -> + val group = + InternalKeyboardShortcutGroup( + label = groupLabel, + items = listOf(internalKeyboardShortcutInfo), + ) + + fetchShortcutCategoryTypeByGestureType(keyGestureType)?.let { + InternalGroupsSource(groups = listOf(group), type = it) + } + } + } + } + + return ungroupedInternalGroupSources + } + + fun getKeyGestureTypeFromShortcutLabel(label: String): Int? { + return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[label] + } + + private fun toInternalKeyboardShortcutInfo( + keyGestureType: Int, + keyTrigger: KeyTrigger, + appLaunchData: AppLaunchData?, + ): InternalKeyboardShortcutInfo? { + fetchShortcutLabelByGestureType(keyGestureType, appLaunchData)?.let { + return InternalKeyboardShortcutInfo( + label = it, + keycode = keyTrigger.keycode, + modifiers = keyTrigger.modifierState, + isCustomShortcut = true, + icon = appLaunchData?.let { fetchShortcutIconByAppLaunchData(appLaunchData) } + ) + } + return null + } + + @SuppressLint("QueryPermissionsNeeded") + private fun fetchShortcutIconByAppLaunchData( + appLaunchData: AppLaunchData + ): Icon? { + val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null + val resolvedActivity = resolveSingleMatchingActivityFrom(intent) + + return if (resolvedActivity == null) { + null + } else { + Icon.createWithResource(context, resolvedActivity.iconResource) + } + } + + private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? { + inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let { + return context.getString(it) + } ?: return null + } + + private fun fetchShortcutLabelByGestureType( + @KeyGestureType keyGestureType: Int, + appLaunchData: AppLaunchData? + ): String? { + inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let { + return context.getString(it) + } + + if (keyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION) { + return fetchShortcutLabelByAppLaunchData(appLaunchData!!) + } + + return null + } + + private fun fetchShortcutLabelByAppLaunchData(appLaunchData: AppLaunchData): String? { + val intent = fetchIntentFromAppLaunchData(appLaunchData) ?: return null + val resolvedActivity = resolveSingleMatchingActivityFrom(intent) + + return if (resolvedActivity == null) { + getIntentCategoryLabel(intent.selector?.categories?.iterator()?.next()) + } else resolvedActivity.loadLabel(userContext.packageManager).toString() + + } + + @SuppressLint("QueryPermissionsNeeded") + private fun resolveSingleMatchingActivityFrom(intent: Intent): ActivityInfo? { + val packageManager = userContext.packageManager + val resolvedActivity = intent.resolveActivityInfo( + packageManager, + /* flags= */ MATCH_DEFAULT_ONLY + ) ?: return null + + val matchesMultipleActivities = + ResolverActivity::class.qualifiedName.equals(resolvedActivity.name) + + return if (matchesMultipleActivities) { + return null + } else resolvedActivity + } + + private fun getIntentCategoryLabel(category: String?): String? { + val categoryLabelRes = when (category.toString()) { + Intent.CATEGORY_APP_BROWSER -> R.string.keyboard_shortcut_group_applications_browser + Intent.CATEGORY_APP_CONTACTS -> R.string.keyboard_shortcut_group_applications_contacts + Intent.CATEGORY_APP_EMAIL -> R.string.keyboard_shortcut_group_applications_email + Intent.CATEGORY_APP_CALENDAR -> R.string.keyboard_shortcut_group_applications_calendar + Intent.CATEGORY_APP_MAPS -> R.string.keyboard_shortcut_group_applications_maps + Intent.CATEGORY_APP_MUSIC -> R.string.keyboard_shortcut_group_applications_music + Intent.CATEGORY_APP_MESSAGING -> R.string.keyboard_shortcut_group_applications_sms + Intent.CATEGORY_APP_CALCULATOR -> R.string.keyboard_shortcut_group_applications_calculator + else -> { + Log.w(TAG, ("No label for app category $category")) + null + } + } + + return if (categoryLabelRes == null){ + return null + } else { + context.getString(categoryLabelRes) + } + } + + private fun fetchIntentFromAppLaunchData(appLaunchData: AppLaunchData): Intent? { + return when (appLaunchData) { + is CategoryData -> Intent.makeMainSelectorActivity( + /* selectorAction= */ ACTION_MAIN, + /* selectorCategory= */ appLaunchData.category + ) + + is RoleData -> getRoleLaunchIntent(appLaunchData.role) + is ComponentData -> resolveComponentNameIntent( + packageName = appLaunchData.packageName, + className = appLaunchData.className + ) + + else -> null + } + } + + private fun resolveComponentNameIntent(packageName: String, className: String): Intent? { + buildIntentFromComponentName(ComponentName(packageName, className))?.let { return it } + buildIntentFromComponentName(ComponentName( + userContext.packageManager.canonicalToCurrentPackageNames(arrayOf(packageName))[0], + className + ))?.let { return it } + return null + } + + private fun buildIntentFromComponentName(componentName: ComponentName): Intent? { + try{ + val flags = + MATCH_DIRECT_BOOT_UNAWARE or MATCH_DIRECT_BOOT_AWARE or MATCH_UNINSTALLED_PACKAGES + // attempt to retrieve activity info to see if a NameNotFoundException is thrown. + userContext.packageManager.getActivityInfo(componentName, flags) + } catch (e: NameNotFoundException) { + Log.w( + TAG, + "Unable to find activity info for componentName: $componentName" + ) + return null + } + + return Intent(ACTION_MAIN).apply { + addCategory(CATEGORY_LAUNCHER) + component = componentName + } + } + + @SuppressLint("NonInjectedService") + private fun getRoleLaunchIntent(role: String): Intent? { + val packageManager = userContext.packageManager + val roleManager = userContext.getSystemService(RoleManager::class.java)!! + if (roleManager.isRoleAvailable(role)) { + roleManager.getDefaultApplication(role)?.let { rolePackage -> + packageManager.getLaunchIntentForPackage(rolePackage)?.let { return it } + ?: Log.w(TAG, "No launch intent for role $role") + } ?: Log.w(TAG, "No default application for role $role, user= ${userContext.user}") + } else { + Log.w(TAG, "Role $role is not available.") + } + return null + } + + private fun fetchShortcutCategoryTypeByGestureType( + @KeyGestureType keyGestureType: Int + ): ShortcutCategoryType? { + return inputGestureMaps.gestureToShortcutCategoryTypeMap[keyGestureType] + } + + private companion object { + private const val TAG = "InputGestureDataUtils" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt index 1c380c26c6c3..30a2f330edf6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt @@ -22,14 +22,8 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_BACK import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME +import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS -import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN @@ -74,13 +68,7 @@ class InputGestureMaps @Inject constructor(private val context: Context) { KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking, // App Category - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to AppCategories, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories, + KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories, ) val gestureToInternalKeyboardShortcutGroupLabelResIdMap = @@ -116,20 +104,14 @@ class InputGestureMaps @Inject constructor(private val context: Context) { R.string.shortcutHelper_category_split_screen, // App Category - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to - R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to - R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to - R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to - R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to R.string.keyboard_shortcut_group_applications, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to + KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications, ) + /** + * App Category shortcut labels are mapped dynamically based on intent + * see [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData] + */ val gestureToInternalKeyboardShortcutInfoLabelResIdMap = mapOf( // System Category @@ -158,22 +140,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) { R.string.system_multitasking_splitscreen_focus_lhs, KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to R.string.system_multitasking_splitscreen_focus_rhs, - - // App Category - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to - R.string.keyboard_shortcut_group_applications_calculator, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to - R.string.keyboard_shortcut_group_applications_calendar, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to - R.string.keyboard_shortcut_group_applications_browser, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to - R.string.keyboard_shortcut_group_applications_contacts, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to - R.string.keyboard_shortcut_group_applications_email, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to - R.string.keyboard_shortcut_group_applications_maps, - KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to - R.string.keyboard_shortcut_group_applications_sms, ) val shortcutLabelToKeyGestureTypeMap: Map<String, Int> diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 7d9e010e31fa..272491850c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -115,6 +115,7 @@ import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEachIndexed import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.painter.rememberDrawablePainter +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo @@ -126,7 +127,6 @@ import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState import com.android.systemui.res.R import kotlinx.coroutines.delay -import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel @Composable fun ShortcutHelper( @@ -189,7 +189,7 @@ private fun ActiveShortcutHelper( onKeyboardSettingsClicked, shortcutsUiState.isShortcutCustomizerFlagEnabled, onCustomizationRequested, - shortcutsUiState.shouldShowResetButton + shortcutsUiState.shouldShowResetButton, ) } } @@ -380,7 +380,7 @@ private fun ShortcutHelperTwoPane( onKeyboardSettingsClicked: () -> Unit, isShortcutCustomizerFlagEnabled: Boolean, onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {}, - shouldShowResetButton: Boolean + shouldShowResetButton: Boolean, ) { val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType } var isCustomizing by remember { mutableStateOf(false) } @@ -801,7 +801,10 @@ private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, - modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp), + modifier = + Modifier.align(Alignment.Center).padding(horizontal = 12.dp).semantics { + hideFromAccessibility() + }, style = MaterialTheme.typography.titleSmall, ) } @@ -825,7 +828,7 @@ private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) { Spacer(Modifier.width(spacing)) Text( text = stringResource(R.string.shortcut_helper_key_combinations_or_separator), - modifier = Modifier.align(Alignment.CenterVertically), + modifier = Modifier.align(Alignment.CenterVertically).semantics { hideFromAccessibility() }, style = MaterialTheme.typography.titleSmall, ) Spacer(Modifier.width(spacing)) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt index 71f29c0062bc..d335a1806a6d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -20,12 +20,12 @@ import android.content.Context import android.content.Intent import android.provider.Settings import android.util.Log -import com.android.systemui.Flags.glanceableHubShortcutButton import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.communal.data.repository.CommunalSceneRepository import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.dagger.SysUISingleton @@ -46,6 +46,7 @@ constructor( @Application private val context: Context, private val communalSceneRepository: CommunalSceneRepository, private val communalInteractor: CommunalInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val sceneInteractor: SceneInteractor, ) : KeyguardQuickAffordanceConfig { @@ -61,8 +62,7 @@ constructor( override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> get() = flow { emit( - // TODO(b/378113263): Gate on getV2FlagEnabled() when ready. - if (!glanceableHubShortcutButton()) { + if (!communalSettingsInteractor.isV2FlagEnabled()) { Log.i(TAG, "Button hidden on lockscreen: flag not enabled.") KeyguardQuickAffordanceConfig.LockScreenState.Hidden } else if (!communalInteractor.isCommunalEnabled.value) { @@ -81,8 +81,7 @@ constructor( } override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { - // TODO(b/378113263): Gate on getV2FlagEnabled() when ready. - return if (!glanceableHubShortcutButton()) { + return if (!communalSettingsInteractor.isV2FlagEnabled()) { Log.i(TAG, "Button unavailable in picker: flag not enabled.") KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice } else if (!communalInteractor.isCommunalEnabled.value) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index deef2a6c3a4c..a9992112f893 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -21,7 +21,6 @@ import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launchTraced as launch -import com.android.systemui.Flags.communalHubOnMobile import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -49,7 +48,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -178,7 +176,8 @@ constructor( newScene = CommunalScenes.Communal, loggingReason = "FromDreamingTransitionInteractor", transitionKey = - if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade + if (communalSettingsInteractor.isV2FlagEnabled()) + CommunalTransitionKeys.SimpleFade else null, ) } else { @@ -226,8 +225,15 @@ constructor( scope.launch { keyguardInteractor.isAbleToDream - .filter { !it } - .sample(deviceEntryInteractor.isUnlocked, ::Pair) + .filterRelevantKeyguardStateAnd { !it } + .sample( + if (SceneContainerFlag.isEnabled) { + deviceEntryInteractor.isUnlocked + } else { + keyguardInteractor.isKeyguardDismissible + }, + ::Pair, + ) .collect { (_, dismissable) -> // TODO(b/349837588): Add check for -> OCCLUDED. if (dismissable) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 7cd2744cb7dc..f078fe26902e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -256,6 +256,16 @@ constructor( val isTransitioningBetweenDesiredScenes = sceneInteractor.transitionState.value.isTransitioning(fromScene, toScene) + // When in STL A -> B settles in A we can't do the same in KTF as KTF requires us to + // start B -> A to get back to A. [LockscreenSceneTransitionInteractor] will emit these + // steps but because STL is Idle(A) at this point (and never even started a B -> A in + // the first place) [isTransitioningBetweenDesiredScenes] will not be satisfied. We need + // this condition to not filter out the STARTED and FINISHED step of the "artificially" + // reversed B -> A transition. + val belongsToInstantReversedTransition = + sceneInteractor.transitionState.value.isIdle(toScene) && + sceneTransitionPair.value.previousValue.isTransitioning(toScene, fromScene) + // We can't compare the terminal step with the current sceneTransition because // a) STL has no guarantee that it will settle in Idle() when finished/canceled // b) Comparing to Idle(toScene) would make any other FINISHED step settling in @@ -267,7 +277,8 @@ constructor( return@filter isTransitioningBetweenLockscreenStates || isTransitioningBetweenDesiredScenes || - terminalStepBelongsToPreviousTransition + terminalStepBelongsToPreviousTransition || + belongsToInstantReversedTransition } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index 9df293bf2c15..3b99bb4c40fa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -276,7 +276,11 @@ constructor( false } else if ( currentState == KeyguardState.DREAMING && - deviceEntryInteractor.get().isUnlocked.value + if (SceneContainerFlag.isEnabled) { + deviceEntryInteractor.get().isUnlocked.value + } else { + keyguardInteractor.isKeyguardDismissible.value + } ) { // Dreams dismiss keyguard and return to GONE if they can. false diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 2fda2013d6f5..d33ad8f80021 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -122,21 +122,20 @@ public class MediaProjectionPermissionActivity extends Activity { final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( EXTRA_USER_REVIEW_GRANTED_CONSENT, false); - if (com.android.systemui.Flags.mediaProjectionRequestAttributionFix()) { - mPackageName = getLaunchedFromPackage(); - } else { - mPackageName = getCallingPackage(); - } - // This activity is launched directly by an app, or system server. System server provides - // the package name through the intent if so. - if (mPackageName == null || ( - com.android.systemui.Flags.mediaProjectionRequestAttributionFix() - && getCallingPackage() == null)) { + // The original requester of this activity start + mPackageName = getLaunchedFromPackage(); + + // This activity is launched directly by using startActivity(), + // thus getCallingPackage() will be null. + if (getCallingPackage() == null) { + // System server provides the package name through the intent if so and is able to get + // the result back. Other applications can't. if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) { mPackageName = launchingIntent.getStringExtra( EXTRA_PACKAGE_REUSING_GRANTED_CONSENT); } else { + // The activity was not launched for result, we abort here finishAsCancelled(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index ba3357c8b591..7c7f48e0d37b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -19,6 +19,8 @@ import android.content.Context; import android.content.res.Resources; import android.provider.Settings; +import androidx.annotation.NonNull; + import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.res.R; @@ -80,6 +82,12 @@ public interface QSHost { void addTile(ComponentName tile); /** + * Click on a tile. Used by external commands + * @param tile the component name of the {@link android.service.quicksettings.TileService} + */ + void clickTile(@NonNull ComponentName tile); + + /** * Adds a custom tile to the set of current tiles. * @param tile the component name of the {@link android.service.quicksettings.TileService} * @param end if true, the tile will be added at the end. If false, at the beginning. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt index 0d464f5a0936..dc3b58247152 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -19,11 +19,13 @@ package com.android.systemui.qs import android.content.ComponentName import android.content.Context import androidx.annotation.GuardedBy +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.external.TileServiceRequestController +import com.android.systemui.qs.flags.QsInCompose import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository @@ -32,7 +34,6 @@ import com.android.systemui.shade.ShadeDisplayAware import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import com.android.app.tracing.coroutines.launchTraced as launch /** * Adapter to determine what real class to use for classes that depend on [QSHost]. @@ -135,4 +136,12 @@ constructor( override fun indexOf(tileSpec: String): Int { return specs.indexOf(tileSpec) } + + override fun clickTile(tile: ComponentName) { + if (QsInCompose.isUnexpectedlyInLegacyMode()) { + return + } + val spec = TileSpec.create(tile) + interactor.currentTiles.value.firstOrNull { it.spec == spec }?.tile?.click(null) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 9dc21fb89b16..8d9f49e55cea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -47,7 +47,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.verticalScroll @@ -248,7 +247,7 @@ constructor( PlatformTheme(isDarkTheme = true) { ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) { AnimatedVisibility( - visible = viewModel.isQsVisible, + visible = viewModel.isQsVisibleAndAnyShadeExpanded, modifier = Modifier.graphicsLayer { alpha = viewModel.viewAlpha } // Clipping before translation to match QSContainerImpl.onDraw @@ -709,12 +708,7 @@ constructor( GridAnchor() TileGrid( viewModel = containerViewModel.tileGridViewModel, - modifier = - Modifier.fillMaxWidth() - .heightIn( - max = - QuickSettingsShade.Dimensions.GridMaxHeight - ), + modifier = Modifier.fillMaxWidth(), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 02498d69b83d..3c725203a15f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -62,6 +62,7 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.transition.LargeScreenShadeInterpolator import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -101,6 +102,7 @@ constructor( DisableFlagsInteractor: DisableFlagsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator, + private val shadeInteractor: ShadeInteractor, @ShadeDisplayAware configurationInteractor: ConfigurationInteractor, private val largeScreenHeaderHelper: LargeScreenHeaderHelper, private val squishinessInteractor: TileSquishinessInteractor, @@ -129,6 +131,9 @@ constructor( var isQsVisible by mutableStateOf(false) + val isQsVisibleAndAnyShadeExpanded: Boolean + get() = anyShadeExpanded && isQsVisible + // This can only be negative if undefined (in which case it will be -1f), else it will be // in [0, 1]. In some cases, it could be set back to -1f internally to indicate that it's // different to every value in [0, 1]. @@ -429,6 +434,12 @@ constructor( ), ) + private val anyShadeExpanded by + hydrator.hydratedStateOf( + traceName = "anyShadeExpanded", + source = shadeInteractor.isAnyExpanded, + ) + fun applyNewQsScrollerBounds(left: Float, top: Float, right: Float, bottom: Float) { if (usingMedia) { qsMediaHost.currentClipping.set( @@ -503,6 +514,8 @@ constructor( printSection("Quick Settings state") { println("isQSExpanded", isQsExpanded) println("isQSVisible", isQsVisible) + println("anyShadeExpanded", anyShadeExpanded) + println("isQSVisibleAndAnyShadeExpanded", isQsVisibleAndAnyShadeExpanded) println("isQSEnabled", isQsEnabled) println("isCustomizing", containerViewModel.editModeViewModel.isEditing.value) println("inFirstPage", inFirstPage) diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 8ef637545e69..cc872060b827 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -23,12 +23,12 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleOwner +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.Utils import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.globalactions.GlobalActionsDialogLite import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -38,6 +38,7 @@ import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.res.R import com.android.systemui.shade.ShadeDisplayAware +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.util.icuMessageFormat import javax.inject.Inject import javax.inject.Named @@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive -import com.android.app.tracing.coroutines.launchTraced as launch private const val TAG = "FooterActionsViewModel" @@ -113,7 +113,7 @@ class FooterActionsViewModel( class Factory @Inject constructor( - @ShadeDisplayAware private val context: Context, + @ShadeDisplayAware private val context: Context, private val falsingManager: FalsingManager, private val footerActionsInteractor: FooterActionsInteractor, private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, @@ -211,7 +211,7 @@ fun FooterActionsViewModel( false /* if the dismiss should be deferred */ }, null /* cancelAction */, - true /* afterKeyguardGone */ + true, /* afterKeyguardGone */ ) } @@ -269,29 +269,7 @@ fun FooterActionsViewModel( .distinctUntilChanged() val userSwitcher = - footerActionsInteractor.userSwitcherStatus - .map { userSwitcherStatus -> - when (userSwitcherStatus) { - UserSwitcherStatusModel.Disabled -> null - is UserSwitcherStatusModel.Enabled -> { - if (userSwitcherStatus.currentUserImage == null) { - Log.e( - TAG, - "Skipped the addition of user switcher button because " + - "currentUserImage is missing", - ) - return@map null - } - - userSwitcherButtonViewModel( - qsThemedContext, - userSwitcherStatus, - ::onUserSwitcherClicked - ) - } - } - } - .distinctUntilChanged() + userSwitcherViewModel(qsThemedContext, footerActionsInteractor, ::onUserSwitcherClicked) val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked) val power = @@ -311,6 +289,36 @@ fun FooterActionsViewModel( ) } +fun userSwitcherViewModel( + themedContext: Context, + footerActionsInteractor: FooterActionsInteractor, + onUserSwitcherClicked: (Expandable) -> Unit, +): Flow<FooterActionsButtonViewModel?> { + return footerActionsInteractor.userSwitcherStatus + .map { userSwitcherStatus -> + when (userSwitcherStatus) { + UserSwitcherStatusModel.Disabled -> null + is UserSwitcherStatusModel.Enabled -> { + if (userSwitcherStatus.currentUserImage == null) { + Log.e( + TAG, + "Skipped the addition of user switcher button because " + + "currentUserImage is missing", + ) + return@map null + } + + userSwitcherButtonViewModel( + themedContext, + userSwitcherStatus, + onUserSwitcherClicked, + ) + } + } + } + .distinctUntilChanged() +} + fun securityButtonViewModel( config: SecurityButtonConfig, onSecurityButtonClicked: (Context, Expandable) -> Unit, @@ -369,7 +377,7 @@ fun userSwitcherButtonViewModel( private fun userSwitcherContentDescription( qsThemedContext: Context, - currentUser: String? + currentUser: String?, ): String? { return currentUser?.let { user -> qsThemedContext.getString(R.string.accessibility_quick_settings_user, user) @@ -384,13 +392,9 @@ fun settingsButtonViewModel( id = R.id.settings_button_container, Icon.Resource( R.drawable.ic_settings, - ContentDescription.Resource(R.string.accessibility_quick_settings_settings) + ContentDescription.Resource(R.string.accessibility_quick_settings_settings), ), - iconTint = - Utils.getColorAttrDefaultColor( - qsThemedContext, - R.attr.onShadeInactiveVariant, - ), + iconTint = Utils.getColorAttrDefaultColor(qsThemedContext, R.attr.onShadeInactiveVariant), backgroundColor = R.attr.shadeInactive, onSettingsButtonClicked, ) @@ -404,14 +408,14 @@ fun powerButtonViewModel( id = R.id.pm_lite, Icon.Resource( android.R.drawable.ic_lock_power_off, - ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) + ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu), ), iconTint = Utils.getColorAttrDefaultColor( qsThemedContext, - R.attr.onShadeActive, + if (DualShade.isEnabled) R.attr.onShadeInactiveVariant else R.attr.onShadeActive, ), - backgroundColor = R.attr.shadeActive, + backgroundColor = if (DualShade.isEnabled) R.attr.shadeInactive else R.attr.shadeActive, onPowerButtonClicked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index ead38f3f9b52..ef45ae76c20c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -209,7 +209,7 @@ constructor( // TODO(b/250618218): Remove this method once we know the root cause of b/250618218. fun logTileBackgroundColorUpdateIfInternetTile( - tileSpec: String, + tileSpec: String?, state: Int, disabledByPolicy: Boolean, color: Int, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index 1f55ac777de5..f4bf53cafd19 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -21,8 +21,6 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl -import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository -import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor import com.android.systemui.qs.panels.domain.interactor.SizedTilesResetInteractor import com.android.systemui.qs.panels.shared.model.GridLayoutType @@ -49,9 +47,6 @@ interface PanelsModule { ): DefaultLargeTilesRepository @Binds - fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository - - @Binds fun bindEditTilesResetInteractor(impl: SizedTilesResetInteractor): EditTilesResetInteractor @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt index 47c4ffd6a2cc..f17abe888b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -17,28 +17,14 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -interface GridLayoutTypeRepository { - val layout: StateFlow<GridLayoutType> - - fun setLayout(type: GridLayoutType) -} +import kotlinx.coroutines.flow.flowOf @SysUISingleton -class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository { - private val _layout: MutableStateFlow<GridLayoutType> = - MutableStateFlow(PaginatedGridLayoutType) - override val layout = _layout.asStateFlow() +class GridLayoutTypeRepository @Inject constructor() { + val defaultLayoutType = flowOf(PaginatedGridLayoutType) - override fun setLayout(type: GridLayoutType) { - if (_layout.value != type) { - _layout.value = type - } - } + val dualShadeLayoutType = flowOf(InfiniteGridLayoutType) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt index 4af1b2223c4c..e493cbeebae8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt @@ -19,14 +19,23 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.shade.domain.interactor.ShadeModeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest @SysUISingleton -class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) { - val layout: StateFlow<GridLayoutType> = repo.layout - - fun setLayoutType(type: GridLayoutType) { - repo.setLayout(type) - } +@OptIn(ExperimentalCoroutinesApi::class) +class GridLayoutTypeInteractor +@Inject +constructor(private val repo: GridLayoutTypeRepository, shadeModeInteractor: ShadeModeInteractor) { + val layout: Flow<GridLayoutType> = + shadeModeInteractor.shadeMode.flatMapLatest { shadeMode -> + when (shadeMode) { + is ShadeMode.Dual -> repo.dualShadeLayoutType + else -> repo.defaultLayoutType + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index b6dbf4db57a4..39408d3dee72 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -49,9 +49,10 @@ import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing -import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel +import com.android.systemui.qs.panels.ui.compose.toolbar.EditModeButton import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel import com.android.systemui.qs.ui.compose.borderOnFocus import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index c6141a1a7cc2..a05747dd3ba2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollFactory import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clipScrollableContainer import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -138,7 +139,6 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R -import kotlin.math.max import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -148,8 +148,9 @@ object TileType @OptIn(ExperimentalMaterial3Api::class) @Composable private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) { + TopAppBar( - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Black), + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), title = { Text(text = stringResource(id = R.string.qs_edit)) }, navigationIcon = { IconButton(onClick = onStopEditing) { @@ -209,7 +210,15 @@ fun DefaultEditTileGrid( Column( verticalArrangement = spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize().verticalScroll(scrollState).padding(innerPadding), + modifier = + modifier + .fillMaxSize() + // Apply top padding before the scroll so the scrollable doesn't show under + // the + // top bar + .padding(top = innerPadding.calculateTopPadding()) + .clipScrollableContainer(Orientation.Vertical) + .verticalScroll(scrollState), ) { AnimatedContent( targetState = listState.dragInProgress, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt index c2764f9f338b..85db95203b45 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.ui.compose +package com.android.systemui.qs.panels.ui.compose.toolbar import androidx.compose.foundation.shape.CornerSize import androidx.compose.foundation.shape.RoundedCornerShape @@ -30,7 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.systemui.lifecycle.rememberViewModel -import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel import com.android.systemui.qs.ui.compose.borderOnFocus import com.android.systemui.res.R diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt new file mode 100644 index 000000000000..37fa9e799521 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose.toolbar + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.systemui.compose.modifiers.sysuiResTag +import com.android.systemui.lifecycle.rememberViewModel +import com.android.systemui.qs.footer.ui.compose.IconButton +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel + +@Composable +fun Toolbar(toolbarViewModelFactory: ToolbarViewModel.Factory, modifier: Modifier = Modifier) { + val viewModel = rememberViewModel("Toolbar") { toolbarViewModelFactory.create() } + + Row( + modifier = modifier.fillMaxWidth().requiredHeight(48.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + viewModel.userSwitcherViewModel?.let { + IconButton(it, Modifier.sysuiResTag("multi_user_switch")) + } + + EditModeButton(viewModel.editModeButtonViewModelFactory) + + IconButton( + viewModel.settingsButtonViewModel, + Modifier.sysuiResTag("settings_button_container"), + ) + + Spacer(modifier = Modifier.weight(1f)) + IconButton(viewModel.powerButtonViewModel, Modifier.sysuiResTag("pm_lite")) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt index 4a18872ad6f6..3fcb2ab37b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.Hydrator import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt index b033473a91e5..f60621882ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.ui.viewmodel +package com.android.systemui.qs.panels.ui.viewmodel.toolbar import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.plugins.FalsingManager +import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt new file mode 100644 index 000000000000..0fde855f576f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel.toolbar + +import android.content.Context +import android.view.ContextThemeWrapper +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import com.android.systemui.animation.Expandable +import com.android.systemui.classifier.domain.interactor.FalsingInteractor +import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap +import com.android.systemui.globalactions.GlobalActionsDialogLite +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.lifecycle.Hydrator +import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor +import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel +import com.android.systemui.qs.footer.ui.viewmodel.powerButtonViewModel +import com.android.systemui.qs.footer.ui.viewmodel.settingsButtonViewModel +import com.android.systemui.qs.footer.ui.viewmodel.userSwitcherViewModel +import com.android.systemui.res.R +import com.android.systemui.shade.ShadeDisplayAware +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import javax.inject.Provider +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch + +class ToolbarViewModel +@AssistedInject +constructor( + val editModeButtonViewModelFactory: EditModeButtonViewModel.Factory, + private val footerActionsInteractor: FooterActionsInteractor, + private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, + private val falsingInteractor: FalsingInteractor, + @ShadeDisplayAware appContext: Context, +) : ExclusiveActivatable() { + private val qsThemedContext = + ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) + private val hydrator = Hydrator("ToolbarViewModel.hydrator") + + val powerButtonViewModel = powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked) + + val settingsButtonViewModel = + settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked) + + val userSwitcherViewModel: FooterActionsButtonViewModel? by + hydrator.hydratedStateOf( + traceName = "userSwitcherViewModel", + initialValue = null, + source = + userSwitcherViewModel( + qsThemedContext, + footerActionsInteractor, + ::onUserSwitcherClicked, + ), + ) + + override suspend fun onActivated(): Nothing { + coroutineScope { + launch { + try { + globalActionsDialogLite = globalActionsDialogLiteProvider.get() + awaitCancellation() + } finally { + globalActionsDialogLite?.destroy() + } + } + launch { hydrator.activate() } + awaitCancellation() + } + } + + private var globalActionsDialogLite: GlobalActionsDialogLite? by mutableStateOf(null) + + private fun onPowerButtonClicked(expandable: Expandable) { + falsingInteractor.runIfNotFalseTap { + globalActionsDialogLite?.let { + footerActionsInteractor.showPowerMenuDialog(it, expandable) + } + } + } + + private fun onUserSwitcherClicked(expandable: Expandable) { + falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showUserSwitcher(expandable) } + } + + private fun onSettingsButtonClicked(expandable: Expandable) { + falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showSettings(expandable) } + } + + @AssistedFactory + interface Factory { + fun create(): ToolbarViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 17b78ebf106c..e8c4274474e0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.base.interactor import android.annotation.WorkerThread +import com.android.systemui.plugins.qs.TileDetailsViewModel interface QSTileUserActionInteractor<DATA_TYPE> { /** @@ -27,4 +28,17 @@ interface QSTileUserActionInteractor<DATA_TYPE> { * It's safe to run long running computations inside this function. */ @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>) + + /** + * Provides the [TileDetailsViewModel] for constructing the corresponding details view. + * + * This property is defined here to reuse the business logic. For example, reusing the user + * long-click as the go-to-settings callback in the details view. + * Subclasses can override this property to provide a specific [TileDetailsViewModel] + * implementation. + * + * @return The [TileDetailsViewModel] instance, or null if not implemented. + */ + val detailsViewModel: TileDetailsViewModel? + get() = null } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index aeb6cef162b5..224fa104168d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -20,6 +20,7 @@ import android.os.UserHandle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Dumpable import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor @@ -115,6 +116,9 @@ class QSTileViewModelImpl<DATA_TYPE>( .flowOn(backgroundDispatcher) .stateIn(tileScope, SharingStarted.WhileSubscribed(), true) + override val detailsViewModel: TileDetailsViewModel? + get() = userActionInteractor().detailsViewModel + override fun forceUpdate() { tileScope.launch(context = backgroundDispatcher) { forceUpdates.emit(Unit) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index bed802163b1a..dbe1ae90b3f6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -195,8 +195,11 @@ public class InternetDialogController implements AccessPointController.AccessPoi private boolean mHasWifiEntries; private WifiStateWorker mWifiStateWorker; private boolean mHasActiveSubIdOnDds; + private boolean mIsMobileDataEnabled = false; @VisibleForTesting + Map<Integer, ServiceState> mSubIdServiceState = new HashMap<>(); + @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @VisibleForTesting static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f; @@ -453,7 +456,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE); } - if (mCanConfigWifi && !isMobileDataEnabled()) { + if (mCanConfigWifi && !mIsMobileDataEnabled) { if (DEBUG) { Log.d(TAG, "Mobile data off"); } @@ -551,7 +554,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi numLevels += 1; } return getSignalStrengthIcon(subId, mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, - !isMobileDataEnabled()); + !mIsMobileDataEnabled); } Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels, @@ -681,6 +684,12 @@ public class InternetDialogController implements AccessPointController.AccessPoi // sets the non-DDS to be not found to hide its visual return SubscriptionManager.INVALID_SUBSCRIPTION_ID; } + int activeDataSubId = SubscriptionManager.getActiveDataSubscriptionId(); + if (activeDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID + || mDefaultDataSubId == activeDataSubId) { + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo( SubscriptionManager.getActiveDataSubscriptionId()); if (subInfo != null && subInfo.getSubscriptionId() != mDefaultDataSubId @@ -740,7 +749,6 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (!isMobileDataEnabled()) { return context.getString(R.string.mobile_data_off_summary); } - String summary = networkTypeDescription; boolean isForDds = subId == mDefaultDataSubId; int activeSubId = getActiveAutoSwitchNonDdsSubId(); @@ -753,8 +761,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi context.getString( isForDds // if nonDds is active, explains Dds status as poor connection ? (isOnNonDds ? R.string.mobile_data_poor_connection - : R.string.mobile_data_connection_active) - : R.string.mobile_data_temp_connection_active), + : R.string.mobile_data_connection_active) + : R.string.mobile_data_temp_connection_active), networkTypeDescription); } else if (!isDataStateInService(subId)) { summary = context.getString(R.string.mobile_data_no_connection); @@ -963,10 +971,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi * Return {@code true} if mobile data is enabled */ boolean isMobileDataEnabled() { - if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) { - return false; - } - return true; + return mIsMobileDataEnabled; } /** @@ -1019,8 +1024,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi } boolean isDataStateInService(int subId) { - TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); - final ServiceState serviceState = tm.getServiceState(); + final ServiceState serviceState = mSubIdServiceState.getOrDefault(subId, + new ServiceState()); NetworkRegistrationInfo regInfo = (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo( NetworkRegistrationInfo.DOMAIN_PS, @@ -1036,8 +1041,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi return false; } - TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager); - final ServiceState serviceState = tm.getServiceState(); + final ServiceState serviceState = mSubIdServiceState.getOrDefault(subId, + new ServiceState()); return serviceState != null && serviceState.getState() == serviceState.STATE_IN_SERVICE; } @@ -1056,6 +1061,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi final Network activeNetwork = mConnectivityManager.getActiveNetwork(); if (activeNetwork == null) { + Log.d(TAG, "getActiveNetwork is null."); return false; } final NetworkCapabilities networkCapabilities = @@ -1183,14 +1189,16 @@ public class InternetDialogController implements AccessPointController.AccessPoi } private class InternetTelephonyCallback extends TelephonyCallback implements + TelephonyCallback.DataEnabledListener, TelephonyCallback.DataConnectionStateListener, TelephonyCallback.DisplayInfoListener, TelephonyCallback.ServiceStateListener, TelephonyCallback.SignalStrengthsListener, TelephonyCallback.UserMobileDataStateListener, - TelephonyCallback.CarrierNetworkListener{ + TelephonyCallback.CarrierNetworkListener { private final int mSubId; + private InternetTelephonyCallback(int subId) { mSubId = subId; } @@ -1200,6 +1208,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi if (mCallback != null) { mCallback.onServiceStateChanged(serviceState); } + mSubIdServiceState.put(mSubId, serviceState); } @Override @@ -1238,6 +1247,13 @@ public class InternetDialogController implements AccessPointController.AccessPoi mCallback.onCarrierNetworkChange(active); } } + + @Override + public void onDataEnabledChanged(boolean b, int i) { + if (mSubId == mDefaultDataSubId) { + mIsMobileDataEnabled = b; + } + } } private class InternetOnSubscriptionChangedListener diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 0ab533bb9838..378d553065e0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -416,9 +416,10 @@ public class InternetDialogDelegate implements internetContent.mHasEthernet = mInternetDialogController.hasEthernet(); internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled(); internetContent.mHasActiveSubIdOnDds = mInternetDialogController.hasActiveSubIdOnDds(); - internetContent.mIsMobileDataEnabled = mInternetDialogController.isMobileDataEnabled(); internetContent.mIsDeviceLocked = mInternetDialogController.isDeviceLocked(); internetContent.mIsWifiScanEnabled = mInternetDialogController.isWifiScanEnabled(); + internetContent.mActiveAutoSwitchNonDdsSubId = + mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); return internetContent; } @@ -433,7 +434,11 @@ public class InternetDialogDelegate implements private void setOnClickListener(SystemUIDialog dialog) { mMobileNetworkLayout.setOnClickListener(v -> { - int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + int autoSwitchNonDdsSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + if (mDataInternetContent.getValue() != null) { + autoSwitchNonDdsSubId = + mDataInternetContent.getValue().mActiveAutoSwitchNonDdsSubId; + } if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { showTurnOffAutoDataSwitchDialog(dialog, autoSwitchNonDdsSubId); } @@ -524,7 +529,7 @@ public class InternetDialogDelegate implements } } else { mMobileNetworkLayout.setVisibility(View.VISIBLE); - mMobileDataToggle.setChecked(internetContent.mIsMobileDataEnabled); + mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled()); mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId)); String summary = getMobileNetworkSummary(mDefaultDataSubId); if (!TextUtils.isEmpty(summary)) { @@ -549,9 +554,9 @@ public class InternetDialogDelegate implements ? R.color.connected_network_primary_color : R.color.disconnected_network_primary_color; mMobileToggleDivider.setBackgroundColor(dialog.getContext().getColor(primaryColor)); - // Display the info for the non-DDS if it's actively being used - int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId(); + int autoSwitchNonDdsSubId = internetContent.mActiveAutoSwitchNonDdsSubId; + int nonDdsVisibility = autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID ? View.VISIBLE : View.GONE; @@ -983,8 +988,8 @@ public class InternetDialogDelegate implements boolean mIsCarrierNetworkActive = false; boolean mIsWifiEnabled = false; boolean mHasActiveSubIdOnDds = false; - boolean mIsMobileDataEnabled = false; boolean mIsDeviceLocked = false; boolean mIsWifiScanEnabled = false; + int mActiveAutoSwitchNonDdsSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt index a963b2875154..c4f9515b819f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt @@ -18,10 +18,13 @@ package com.android.systemui.qs.tiles.impl.internet.domain.interactor import android.content.Intent import android.provider.Settings +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel import com.android.systemui.qs.tiles.dialog.InternetDialogManager import com.android.systemui.qs.tiles.dialog.WifiStateWorker import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel @@ -61,11 +64,18 @@ constructor( wifiStateWorker.isWifiEnabled = !wifiStateWorker.isWifiEnabled } is QSTileUserAction.LongClick -> { - qsTileIntentUserActionHandler.handle( - action.expandable, - Intent(Settings.ACTION_WIFI_SETTINGS) - ) + handleLongClick(action.expandable) } } } + + override val detailsViewModel: TileDetailsViewModel = + InternetDetailsViewModel { handleLongClick(null) } + + private fun handleLongClick(expandable:Expandable?){ + qsTileIntentUserActionHandler.handle( + expandable, + Intent(Settings.ACTION_WIFI_SETTINGS) + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt index b1b0001b6361..e8b9926e5cea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.viewmodel import android.os.UserHandle +import com.android.systemui.plugins.qs.TileDetailsViewModel import kotlinx.coroutines.flow.StateFlow /** @@ -37,6 +38,10 @@ interface QSTileViewModel { /** Specifies whether this device currently supports this tile. */ val isAvailable: StateFlow<Boolean> + /** Specifies the [TileDetailsViewModel] for constructing the corresponding details view. */ + val detailsViewModel: TileDetailsViewModel? + get() = null + /** * Notifies about the user change. Implementations should avoid using 3rd party userId sources * and use this value instead. This is to maintain consistent and concurrency-free behaviour diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 9d902d39efff..632eeefcb462 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -27,6 +27,7 @@ import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.TileDetailsViewModel import com.android.systemui.qs.QSHost import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes @@ -154,6 +155,10 @@ constructor( qsTileViewModel.onUserChanged(UserHandle.of(currentUser)) } + override fun getDetailsViewModel(): TileDetailsViewModel? { + return qsTileViewModel.detailsViewModel + } + @Deprecated( "Not needed as {@link com.android.internal.logging.UiEvent} will use #getMetricsSpec", replaceWith = ReplaceWith("getMetricsSpec"), diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt index 62b120332289..91d907952bc6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -38,6 +39,7 @@ constructor( val tileGridViewModel: TileGridViewModel, val editModeViewModel: EditModeViewModel, val detailsViewModel: DetailsViewModel, + val toolbarViewModelFactory: ToolbarViewModel.Factory, ) : ExclusiveActivatable() { val brightnessSliderViewModel = diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 31780a56f7f0..61ac1a029f1f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -41,11 +41,11 @@ import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.theme.PlatformTheme import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Flags -import com.android.systemui.Flags.communalHubOnMobile import com.android.systemui.ambient.touch.TouchMonitor import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -83,6 +83,7 @@ class GlanceableHubContainerController @Inject constructor( private val communalInteractor: CommunalInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, private val communalViewModel: CommunalViewModel, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, @@ -514,7 +515,7 @@ constructor( val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt()) val touchOnSmartspace = lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt()) - val glanceableHubV2 = communalHubOnMobile() + val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled() if ( !hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt index 6f491e7beec8..85b50d3320dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -67,9 +68,17 @@ constructor( Flags.statusBarCallChipNotificationIcon() && state.notificationIconView != null ) { + StatusBarConnectedDisplays.assertInLegacyMode() OngoingActivityChipModel.ChipIcon.StatusBarView( state.notificationIconView ) + } else if ( + StatusBarConnectedDisplays.isEnabled && + Flags.statusBarCallChipNotificationIcon() + ) { + OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon( + state.notificationKey + ) } else { OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt index c57c807360b1..571a3e44d233 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.log.core.Logger import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -100,10 +101,15 @@ constructor( private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? { val statusBarChipIconView = this.statusBarChipIconView if (statusBarChipIconView == null) { - logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) { - str1 = extraLogTag + if (!StatusBarConnectedDisplays.isEnabled) { + logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) { + str1 = extraLogTag + } + // When the flag is disabled, we keep the old behavior of returning null. + // When the flag is enabled, the icon will always be null, and will later be + // fetched in the UI layer using the notification key. + return null } - return null } return NotificationChipModel(key, statusBarChipIconView, whenTime) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt index bc4241d9bca5..4588b19bd720 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt @@ -21,6 +21,6 @@ import com.android.systemui.statusbar.StatusBarIconView /** Modeling all the data needed to render a status bar notification chip. */ data class NotificationChipModel( val key: String, - val statusBarChipIconView: StatusBarIconView, + val statusBarChipIconView: StatusBarIconView?, val whenTime: Long, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index b2f7e2fe7660..2cd5bb339072 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.chips.notification.domain.model.Notificati import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -50,7 +51,14 @@ constructor( /** Converts the notification to the [OngoingActivityChipModel] object. */ private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown { StatusBarNotifChips.assertInNewMode() - val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView) + val icon = + if (this.statusBarChipIconView != null) { + StatusBarConnectedDisplays.assertInLegacyMode() + OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView) + } else { + StatusBarConnectedDisplays.assertInNewMode() + OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key) + } // TODO(b/364653005): Use the notification color if applicable. val colors = ColorsModel.Themed val onClickListener = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index 730784a46001..cf69d401df60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -32,11 +32,13 @@ import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifCh import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.view.ChipChronometer +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore /** Binder for ongoing activity chip views. */ object OngoingActivityChipBinder { /** Binds the given [chipModel] data to the given [chipView]. */ - fun bind(chipModel: OngoingActivityChipModel, chipView: View) { + fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) { val chipContext = chipView.context val chipDefaultIconView: ImageView = chipView.requireViewById(R.id.ongoing_activity_chip_icon) @@ -51,7 +53,7 @@ object OngoingActivityChipBinder { when (chipModel) { is OngoingActivityChipModel.Shown -> { // Data - setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView) + setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore) setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView) chipView.setOnClickListener(chipModel.onClickListener) updateChipPadding( @@ -85,6 +87,7 @@ object OngoingActivityChipBinder { chipModel: OngoingActivityChipModel.Shown, backgroundView: ChipBackgroundContainer, defaultIconView: ImageView, + iconViewStore: IconViewStore?, ) { // Always remove any previously set custom icon. If we have a new custom icon, we'll re-add // it. @@ -108,38 +111,62 @@ object OngoingActivityChipBinder { defaultIconView.untintView() } is OngoingActivityChipModel.ChipIcon.StatusBarView -> { - // Hide the default icon since we'll show this custom icon instead. - defaultIconView.visibility = View.GONE - - // Add the new custom icon: - // 1. Set up the right visual params. - val iconView = icon.impl - with(iconView) { - id = CUSTOM_ICON_VIEW_ID - // TODO(b/354930838): Update the content description to not include "phone" and - // maybe include the app name. - contentDescription = - context.resources.getString(R.string.ongoing_phone_call_content_description) - tintView(iconTint) + StatusBarConnectedDisplays.assertInLegacyMode() + setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView) + } + is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> { + StatusBarConnectedDisplays.assertInNewMode() + val iconView = fetchStatusBarIconView(iconViewStore, icon) + if (iconView == null) { + // This means that the notification key doesn't exist anymore. + return } + setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView) + } + } + } - // 2. If we just reinflated the view, we may need to detach the icon view from the - // old chip before we reattach it to the new one. - // See also: NotificationIconContainerViewBinder#bindIcons. - val currentParent = iconView.parent as? ViewGroup - if (currentParent != null && currentParent != backgroundView) { - currentParent.removeView(iconView) - currentParent.removeTransientView(iconView) - } + private fun fetchStatusBarIconView( + iconViewStore: IconViewStore?, + icon: OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon, + ): StatusBarIconView? { + StatusBarConnectedDisplays.assertInNewMode() + if (iconViewStore == null) { + throw IllegalStateException("Store should always be non-null when flag is enabled.") + } + return iconViewStore.iconView(icon.notificationKey) + } - // 3: Add the icon as the starting view. - backgroundView.addView( - iconView, - /* index= */ 0, - generateCustomIconLayoutParams(iconView), - ) - } + private fun setStatusBarIconView( + defaultIconView: ImageView, + iconView: StatusBarIconView, + iconTint: Int, + backgroundView: ChipBackgroundContainer, + ) { + // Hide the default icon since we'll show this custom icon instead. + defaultIconView.visibility = View.GONE + + // 1. Set up the right visual params. + with(iconView) { + id = CUSTOM_ICON_VIEW_ID + // TODO(b/354930838): Update the content description to not include "phone" and maybe + // include the app name. + contentDescription = + context.resources.getString(R.string.ongoing_phone_call_content_description) + tintView(iconTint) + } + + // 2. If we just reinflated the view, we may need to detach the icon view from the old chip + // before we reattach it to the new one. + // See also: NotificationIconContainerViewBinder#bindIcons. + val currentParent = iconView.parent as? ViewGroup + if (currentParent != null && currentParent != backgroundView) { + currentParent.removeView(iconView) + currentParent.removeTransientView(iconView) } + + // 3: Add the icon as the starting view. + backgroundView.addView(iconView, /* index= */ 0, generateCustomIconLayoutParams(iconView)) } private fun View.getCustomIconView(): StatusBarIconView? { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index cf07af1fc5dd..2dce4e38b803 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays /** Model representing the display of an ongoing activity as a chip in the status bar. */ sealed class OngoingActivityChipModel { @@ -132,6 +133,17 @@ sealed class OngoingActivityChipModel { "OngoingActivityChipModel.ChipIcon.StatusBarView created even though " + "Flags.statusBarCallChipNotificationIcon is not enabled" } + StatusBarConnectedDisplays.assertInLegacyMode() + } + } + + /** + * The icon is a custom icon, which is set on a notification, and can be looked up using the + * provided [notificationKey]. The icon was likely created by an external app. + */ + data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon { + init { + StatusBarConnectedDisplays.assertInNewMode() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt index ef7b1c3d562e..04458f3a7168 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt @@ -14,10 +14,13 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification import android.os.UserHandle +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.KeyguardUpdateMonitor import com.android.server.notification.Flags.screenshareNotificationHiding import com.android.systemui.dagger.qualifiers.Application @@ -44,9 +47,9 @@ import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapNotNull -import com.android.app.tracing.coroutines.launchTraced as launch @Module(includes = [PrivateSensitiveContentCoordinatorModule::class]) interface SensitiveContentCoordinatorModule @@ -80,6 +83,7 @@ constructor( DynamicPrivacyController.Listener, OnBeforeRenderListListener { private var inTransitionFromLockedToGone = false + private var canSwipeToEnter = false private val onSensitiveStateChanged = Runnable() { invalidateList("onSensitiveStateChanged") } @@ -98,7 +102,9 @@ constructor( } override fun attach(pipeline: NotifPipeline) { - dynamicPrivacyController.addListener(this) + if (!SceneContainerFlag.isEnabled) { + dynamicPrivacyController.addListener(this) + } if (screenshareNotificationHiding()) { sensitiveNotificationProtectionController.registerSensitiveStateListener( onSensitiveStateChanged @@ -128,6 +134,15 @@ constructor( invalidateList("inTransitionFromLockedToGoneChanged") } } + scope.launch { + deviceEntryInteractor.canSwipeToEnter.collect { + val canSwipeToEnter = it ?: false + if (this@SensitiveContentCoordinatorImpl.canSwipeToEnter != canSwipeToEnter) { + this@SensitiveContentCoordinatorImpl.canSwipeToEnter = canSwipeToEnter + invalidateList("canSwipeToEnterChanged") + } + } + } } } @@ -168,7 +183,9 @@ constructor( (devicePublic && !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) || isSensitiveContentProtectionActive - val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked + val dynamicallyUnlocked = + if (SceneContainerFlag.isEnabled) canSwipeToEnter + else dynamicPrivacyController.isDynamicallyUnlocked for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) { val notifUserId = entry.sbn.user.identifier val userLockscreen = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 98ce163b81ca..b56a838a80a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -162,7 +162,9 @@ constructor( val sbIcon = iconBuilder.createIconView(entry) sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE val sbChipIcon: StatusBarIconView? - if (Flags.statusBarCallChipNotificationIcon()) { + if ( + Flags.statusBarCallChipNotificationIcon() && !StatusBarConnectedDisplays.isEnabled + ) { sbChipIcon = iconBuilder.createIconView(entry) sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index c8e18a8fe5ca..99edf652f289 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static android.app.Flags.notificationsRedesignTemplates; + import android.app.Notification; import android.content.Context; import android.content.res.Configuration; @@ -171,7 +173,9 @@ public class NotificationChildrenContainer extends ViewGroup R.dimen.notification_children_container_margin_top); mNotificationTopPadding = res.getDimensionPixelOffset( R.dimen.notification_children_container_top_padding); - mHeaderHeight = mNotificationHeaderMargin + mNotificationTopPadding; + mHeaderHeight = notificationsRedesignTemplates() + ? res.getDimensionPixelSize(R.dimen.notification_2025_header_height) + : mNotificationHeaderMargin + mNotificationTopPadding; mCollapsedBottomPadding = res.getDimensionPixelOffset( R.dimen.notification_children_collapsed_bottom_padding); mEnableShadowOnChildNotifications = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index ba707a5cd0b1..245b1d29fb79 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -429,9 +429,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { }; /** - * Recalculate sensitiveness without animation; called when waking up while keyguard occluded. + * Recalculate sensitiveness without animation; called when waking up while keyguard occluded, + * or whenever we update the Lockscreen public mode. */ - public void updateSensitivenessForOccludedWakeup() { + public void updateSensitivenessWithoutAnimation() { updateSensitivenessWithAnimation(false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java index 969ff1b4ffe7..44075afa6b40 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.stack; +import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK; + import android.annotation.ColorInt; import android.annotation.Nullable; import android.annotation.StringRes; @@ -28,6 +30,8 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import androidx.core.view.ViewCompat; + import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -69,6 +73,13 @@ public class SectionHeaderView extends StackScrollerDecorView { mLabelView.setText(mLabelTextId); } mLabelView.setAccessibilityHeading(true); + ViewCompat.replaceAccessibilityAction( + mLabelView, + ACTION_CLICK, + getResources().getString( + R.string.accessibility_notification_section_header_open_settings), + null + ); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index d1338eadb6b5..f2ef2f0ab48f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -313,6 +313,7 @@ constructor( // if it is volume panel. options.setDisallowEnterPictureInPictureWhileLaunching(true) } + intent.collectExtraIntentKeys() try { result[0] = ActivityTaskManager.getService() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 7f95fb072ede..adfcb710da92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -53,6 +53,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.qs.flags.QsInCompose; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; @@ -209,10 +210,16 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @Override public void clickTile(ComponentName tile) { - // Can't inject this because it changes with the QS fragment - QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController(); - if (qsPanelController != null) { - qsPanelController.clickTile(tile); + if (QsInCompose.isEnabled()) { + if (tile != null) { + mQSHost.clickTile(tile); + } + } else { + // Can't inject this because it changes with the QS fragment + QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController(); + if (qsPanelController != null) { + qsPanelController.clickTile(tile); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index c6af3280eef1..7bea4800f7fc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2234,6 +2234,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // If the state didn't change, we may still need to update public mode mLockscreenUserManager.updatePublicMode(); + if (SceneContainerFlag.isEnabled()) { + mStackScrollerController.updateSensitivenessWithoutAnimation(); + } } if (mStatusBarStateController.leaveOpenOnKeyguardHide()) { if (!mStatusBarStateController.isKeyguardRequested()) { @@ -2681,7 +2684,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // So if AOD is off or unsupported we need to trigger these updates at screen on // when the keyguard is occluded. mLockscreenUserManager.updatePublicMode(); - mStackScrollerController.updateSensitivenessForOccludedWakeup(); + mStackScrollerController.updateSensitivenessWithoutAnimation(); } if (mLaunchCameraWhenFinishedWaking) { startLaunchTransitionTimeout(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index 1cca3ae0a2c0..d7cc65d22663 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -180,6 +180,7 @@ constructor( // if it is volume panel. options.setDisallowEnterPictureInPictureWhileLaunching(true) } + intent.collectExtraIntentKeys() try { result[0] = ActivityTaskManager.getService() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index e4768e83f7d0..724ba8cab352 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -377,6 +377,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); mHomeStatusBarViewBinder.bind( + view.getContext().getDisplayId(), mStatusBar, mHomeStatusBarViewModel, /* systemEventChipAnimateIn */ null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index 216630409160..c57cede754d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -260,6 +260,7 @@ constructor( startTimeMs = currentInfo.callStartTime, notificationIconView = icon, intent = currentInfo.intent, + notificationKey = currentInfo.key, ) } else { return OngoingCallModel.NoCall diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt index 1f7bd1418543..4b71c0268f11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt @@ -90,6 +90,7 @@ class OngoingCallInteractor @Inject constructor( startTimeMs = model.whenTime, notificationIconView = model.statusBarChipIconView, intent = model.contentIntent, + notificationKey = model.key, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt index c2c91b27d074..1a5dcc16f3db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt @@ -46,5 +46,6 @@ sealed interface OngoingCallModel { val startTimeMs: Long, val notificationIconView: StatusBarIconView?, val intent: PendingIntent?, + val notificationKey: String, ) : OngoingCallModel } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index 08a98c397d5f..12f578c525fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -161,6 +161,13 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), true) + /** True if any known mobile network is currently using a non terrestrial network */ + val isAnyConnectionNtn = + iconsInteractor.icons.aggregateOver(selector = { it.isNonTerrestrial }, false) { + nonTerrestrialNetworks -> + nonTerrestrialNetworks.any { it == true } + } + companion object { const val TAG = "DeviceBasedSatelliteInteractor" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt index f3d513940bcf..ea915efb17be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt @@ -114,34 +114,39 @@ constructor( private val showIcon = if (interactor.isOpportunisticSatelliteIconEnabled) { - canShowIcon - .flatMapLatest { canShow -> - if (!canShow) { - flowOf(false) - } else { - combine( - shouldShowIconForOosAfterHysteresis, - interactor.connectionState, - interactor.isWifiActive, - airplaneModeRepository.isAirplaneMode, - ) { showForOos, connectionState, isWifiActive, isAirplaneMode -> - if (isWifiActive || isAirplaneMode) { - false - } else { - showForOos || - connectionState == SatelliteConnectionState.On || - connectionState == SatelliteConnectionState.Connected + canShowIcon + .flatMapLatest { canShow -> + if (!canShow) { + flowOf(false) + } else { + combine( + shouldShowIconForOosAfterHysteresis, + interactor.isAnyConnectionNtn, + interactor.connectionState, + interactor.isWifiActive, + airplaneModeRepository.isAirplaneMode, + ) { showForOos, anyNtn, connectionState, isWifiActive, isAirplaneMode -> + // anyNtn means that there is some mobile network using ntn, and the + // mobile icon will show its own satellite icon + if (isWifiActive || isAirplaneMode || anyNtn) { + false + } else { + // Show for out of service (which has a hysteresis), or ignore + // the hysteresis if we're already connected + showForOos || + connectionState == SatelliteConnectionState.On || + connectionState == SatelliteConnectionState.Connected + } } } } - } - .distinctUntilChanged() - .logDiffsForTable( - tableLog, - columnPrefix = "vm", - columnName = COL_VISIBLE, - initialValue = false, - ) + .distinctUntilChanged() + .logDiffsForTable( + tableLog, + columnPrefix = "vm", + columnName = COL_VISIBLE, + initialValue = false, + ) } else { flowOf(false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt index 72df02714d43..d9b2bd1d66c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt @@ -20,9 +20,9 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.view.View import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached @@ -31,16 +31,19 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.core.StatusBarRootModernization import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim +import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedDisplaysStatusBarNotificationIconViewStore import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel import javax.inject.Inject +import kotlinx.coroutines.launch /** * Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel]. @@ -56,6 +59,7 @@ interface HomeStatusBarViewBinder { * to support the chip animations. */ fun bind( + displayId: Int, view: View, viewModel: HomeStatusBarViewModel, systemEventChipAnimateIn: ((View) -> Unit)?, @@ -65,8 +69,13 @@ interface HomeStatusBarViewBinder { } @SysUISingleton -class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinder { +class HomeStatusBarViewBinderImpl +@Inject +constructor( + private val viewStoreFactory: ConnectedDisplaysStatusBarNotificationIconViewStore.Factory +) : HomeStatusBarViewBinder { override fun bind( + displayId: Int, view: View, viewModel: HomeStatusBarViewModel, systemEventChipAnimateIn: ((View) -> Unit)?, @@ -75,6 +84,14 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde ) { view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { + val iconViewStore = + if (StatusBarConnectedDisplays.isEnabled) { + viewStoreFactory.create(displayId).also { + lifecycleScope.launch { it.activate() } + } + } else { + null + } launch { viewModel.isTransitioningFromLockscreenToOccluded.collect { listener.onStatusBarVisibilityMaybeChanged() @@ -102,7 +119,11 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde view.requireViewById(R.id.ongoing_activity_chip_primary) launch { viewModel.primaryOngoingActivityChip.collect { primaryChipModel -> - OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView) + OngoingActivityChipBinder.bind( + primaryChipModel, + primaryChipView, + iconViewStore, + ) if (StatusBarRootModernization.isEnabled) { when (primaryChipModel) { is OngoingActivityChipModel.Shown -> @@ -142,10 +163,18 @@ class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinde view.requireViewById(R.id.ongoing_activity_chip_secondary) launch { viewModel.ongoingActivityChips.collect { chips -> - OngoingActivityChipBinder.bind(chips.primary, primaryChipView) + OngoingActivityChipBinder.bind( + chips.primary, + primaryChipView, + iconViewStore, + ) // TODO(b/364653005): Don't show the secondary chip if there isn't // enough space for it. - OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView) + OngoingActivityChipBinder.bind( + chips.secondary, + secondaryChipView, + iconViewStore, + ) if (StatusBarRootModernization.isEnabled) { primaryChipView.adjustVisibility(chips.primary.toVisibilityModel()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt index 812e0eb0908f..5614d82c2e4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt @@ -176,6 +176,7 @@ fun StatusBarRoot( // This binder handles everything else scope.launch { statusBarViewBinder.bind( + context.displayId, phoneStatusBarView, statusBarViewModel, eventAnimationInteractor::animateStatusBarContentForChipEnter, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt index b83613ba4f8c..40719185e290 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt @@ -20,7 +20,6 @@ import android.media.AudioManager import android.media.AudioManager.RINGER_MODE_NORMAL import android.media.AudioManager.RINGER_MODE_SILENT import android.media.AudioManager.RINGER_MODE_VIBRATE -import android.provider.Settings import com.android.settingslib.volume.data.repository.AudioSystemRepository import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.plugins.VolumeDialogController @@ -66,11 +65,6 @@ constructor( } }, currentRingerMode = RingerMode(state.ringerModeInternal), - isEnabled = - !(state.zenMode == Settings.Global.ZEN_MODE_ALARMS || - state.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS || - (state.zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS && - state.disallowRinger)), isMuted = it.level == 0 || it.muted, level = it.level, levelMax = it.levelMax, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt index 3c24e02f3732..84a82805aace 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/shared/model/VolumeDialogRingerModel.kt @@ -23,8 +23,6 @@ data class VolumeDialogRingerModel( val availableModes: List<RingerMode>, /** Current ringer mode internal */ val currentRingerMode: RingerMode, - /** whether the ringer is allowed given the current ZenMode */ - val isEnabled: Boolean, /** Whether the current ring stream level is zero or the controller state is muted */ val isMuted: Boolean, /** Ring stream level */ diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt index 3bd2721dcfe5..9eee91beda51 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt @@ -34,6 +34,7 @@ import com.android.settingslib.Utils import com.android.systemui.res.R import com.android.systemui.util.children import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope +import com.android.systemui.volume.dialog.ringer.ui.util.VolumeDialogRingerDrawerTransitionListener import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonUiModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState @@ -42,6 +43,7 @@ import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelSta import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel import com.android.systemui.volume.dialog.ui.utils.suspendAnimate import javax.inject.Inject +import kotlin.properties.Delegates import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.launchIn @@ -71,6 +73,27 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { val drawerContainer = view.requireViewById<MotionLayout>(R.id.volume_ringer_drawer) val unselectedButtonUiModel = RingerButtonUiModel.getUnselectedButton(view.context) val selectedButtonUiModel = RingerButtonUiModel.getSelectedButton(view.context) + val volumeDialogBgSmallRadius = + view.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_background_square_corner_radius + ) + val volumeDialogBgFullRadius = + view.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_background_corner_radius + ) + var backgroundAnimationProgress: Float by + Delegates.observable(0F) { _, _, progress -> + volumeDialogBackgroundView.applyCorners( + fullRadius = volumeDialogBgFullRadius, + diff = volumeDialogBgFullRadius - volumeDialogBgSmallRadius, + progress, + ) + } + val ringerDrawerTransitionListener = VolumeDialogRingerDrawerTransitionListener { + backgroundAnimationProgress = it + } + drawerContainer.setTransitionListener(ringerDrawerTransitionListener) + volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate() viewModel.ringerViewModel .onEach { ringerState -> when (ringerState) { @@ -87,10 +110,8 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { selectedButtonUiModel, unselectedButtonUiModel, ) + ringerDrawerTransitionListener.setProgressChangeEnabled(true) drawerContainer.closeDrawer(uiModel.currentButtonIndex) - volumeDialogBackgroundView.setBackgroundResource( - R.drawable.volume_dialog_background - ) } is RingerDrawerState.Closed -> { @@ -103,11 +124,31 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { uiModel, selectedButtonUiModel, unselectedButtonUiModel, + onProgressChanged = { progress, isReverse -> + // Let's make button progress when switching matches + // motionLayout transition progress. When full radius, + // progress is 0.0. When small radius, progress is 1.0. + backgroundAnimationProgress = + if (isReverse) { + 1F - progress + } else { + progress + } + }, ) { + if ( + uiModel.currentButtonIndex == + uiModel.availableButtons.size - 1 + ) { + ringerDrawerTransitionListener.setProgressChangeEnabled( + false + ) + } else { + ringerDrawerTransitionListener.setProgressChangeEnabled( + true + ) + } drawerContainer.closeDrawer(uiModel.currentButtonIndex) - volumeDialogBackgroundView.setBackgroundResource( - R.drawable.volume_dialog_background - ) } } } @@ -120,16 +161,18 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { unselectedButtonUiModel, ) // Open drawer - drawerContainer.transitionToState( - R.id.volume_dialog_ringer_drawer_open - ) if ( - uiModel.currentButtonIndex != uiModel.availableButtons.size - 1 + uiModel.currentButtonIndex == uiModel.availableButtons.size - 1 ) { - volumeDialogBackgroundView.setBackgroundResource( - R.drawable.volume_dialog_background_small_radius - ) + ringerDrawerTransitionListener.setProgressChangeEnabled(false) + } else { + ringerDrawerTransitionListener.setProgressChangeEnabled(true) } + drawerContainer.transitionToState( + R.id.volume_dialog_ringer_drawer_open + ) + volumeDialogBackgroundView.background = + volumeDialogBackgroundView.background.mutate() } } } @@ -150,6 +193,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { uiModel: RingerViewModel, selectedButtonUiModel: RingerButtonUiModel, unselectedButtonUiModel: RingerButtonUiModel, + onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> }, onAnimationEnd: Runnable? = null, ) { ensureChildCount(R.layout.volume_ringer_button, uiModel.availableButtons.size) @@ -177,10 +221,26 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { CLOSE_DRAWER_DELAY, ) } - - // We only need to execute on roundness animation end once. - selectedButton.animateTo(selectedButtonUiModel, roundnessAnimationEndListener) - unselectedButton.animateTo(unselectedButtonUiModel) + // We only need to execute on roundness animation end and volume dialog background + // progress update once because these changes should be applied once on volume dialog + // background and ringer drawer views. + selectedButton.animateTo( + selectedButtonUiModel, + if (uiModel.currentButtonIndex == count - 1) { + onProgressChanged + } else { + { _, _ -> } + }, + roundnessAnimationEndListener, + ) + unselectedButton.animateTo( + unselectedButtonUiModel, + if (previousIndex == count - 1) { + onProgressChanged + } else { + { _, _ -> } + }, + ) } else { bindButtons(viewModel, uiModel, onAnimationEnd) } @@ -366,6 +426,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { private suspend fun ImageButton.animateTo( ringerButtonUiModel: RingerButtonUiModel, + onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> }, roundnessAnimationEndListener: DynamicAnimation.OnAnimationEndListener? = null, ) { val roundnessAnimation = @@ -376,6 +437,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { ringerButtonUiModel.cornerRadius - (background as GradientDrawable).cornerRadius val roundnessAnimationUpdateListener = DynamicAnimation.OnAnimationUpdateListener { _, value, _ -> + onProgressChanged(value, cornerRadiusDiff > 0F) (background as GradientDrawable).cornerRadius = radius + value * cornerRadiusDiff background.invalidateSelf() } @@ -406,4 +468,9 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { ) } } + + private fun View.applyCorners(fullRadius: Int, diff: Int, progress: Float) { + (background as GradientDrawable).cornerRadius = fullRadius - progress * diff + background.invalidateSelf() + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt new file mode 100644 index 000000000000..6e3db0afb483 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/VolumeDialogRingerDrawerTransitionListener.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.ringer.ui.util + +import androidx.constraintlayout.motion.widget.MotionLayout + +class VolumeDialogRingerDrawerTransitionListener(private val onProgressChanged: (Float) -> Unit) : + MotionLayout.TransitionListener { + + private var notifyProgressChangeEnabled = true + + fun setProgressChangeEnabled(enabled: Boolean) { + notifyProgressChangeEnabled = enabled + } + + override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {} + + override fun onTransitionChange( + motionLayout: MotionLayout?, + startId: Int, + endId: Int, + progress: Float, + ) { + if (notifyProgressChangeEnabled) { + onProgressChanged(progress) + } + } + + override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {} + + override fun onTransitionTrigger( + motionLayout: MotionLayout?, + triggerId: Int, + positive: Boolean, + progress: Float, + ) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt index e646636dd2a2..627d75ee108d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt @@ -21,10 +21,13 @@ import android.media.AudioAttributes import android.media.AudioManager.RINGER_MODE_NORMAL import android.media.AudioManager.RINGER_MODE_SILENT import android.media.AudioManager.RINGER_MODE_VIBRATE +import android.media.AudioManager.STREAM_RING import android.os.VibrationEffect import android.widget.Toast import com.android.internal.R as internalR import com.android.settingslib.Utils +import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor +import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -57,7 +60,8 @@ constructor( @Application private val applicationContext: Context, @VolumeDialog private val coroutineScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, - private val interactor: VolumeDialogRingerInteractor, + soundPolicyInteractor: NotificationsSoundPolicyInteractor, + private val ringerInteractor: VolumeDialogRingerInteractor, private val vibrator: VibratorHelper, private val volumeDialogLogger: VolumeDialogLogger, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @@ -66,10 +70,14 @@ constructor( private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial) val ringerViewModel: StateFlow<RingerViewModelState> = - combine(interactor.ringerModel, drawerState) { ringerModel, state -> + combine( + soundPolicyInteractor.isZenMuted(AudioStream(STREAM_RING)), + ringerInteractor.ringerModel, + drawerState, + ) { isZenMuted, ringerModel, state -> level = ringerModel.level levelMax = ringerModel.levelMax - ringerModel.toViewModel(state) + ringerModel.toViewModel(state, isZenMuted) } .flowOn(backgroundDispatcher) .stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable) @@ -90,7 +98,7 @@ constructor( Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value) provideTouchFeedback(ringerMode) maybeShowToast(ringerMode) - interactor.setRingerMode(ringerMode) + ringerInteractor.setRingerMode(ringerMode) } visibilityInteractor.resetDismissTimeout() drawerState.value = @@ -113,7 +121,7 @@ constructor( private fun provideTouchFeedback(ringerMode: RingerMode) { when (ringerMode.value) { RINGER_MODE_NORMAL -> { - interactor.scheduleTouchFeedback() + ringerInteractor.scheduleTouchFeedback() null } RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK) @@ -123,7 +131,8 @@ constructor( } private fun VolumeDialogRingerModel.toViewModel( - drawerState: RingerDrawerState + drawerState: RingerDrawerState, + isZenMuted: Boolean, ): RingerViewModelState { val currentIndex = availableModes.indexOf(currentRingerMode) if (currentIndex == -1) { @@ -132,10 +141,11 @@ constructor( return if (currentIndex == -1 || isSingleVolume) { RingerViewModelState.Unavailable } else { - toButtonViewModel(currentRingerMode, isSelectedButton = true)?.let { + toButtonViewModel(currentRingerMode, isZenMuted, isSelectedButton = true)?.let { RingerViewModelState.Available( RingerViewModel( - availableButtons = availableModes.map { mode -> toButtonViewModel(mode) }, + availableButtons = + availableModes.map { mode -> toButtonViewModel(mode, isZenMuted) }, currentButtonIndex = currentIndex, selectedButton = it, drawerState = drawerState, @@ -147,6 +157,7 @@ constructor( private fun VolumeDialogRingerModel.toButtonViewModel( ringerMode: RingerMode, + isZenMuted: Boolean, isSelectedButton: Boolean = false, ): RingerButtonViewModel? { return when (ringerMode.value) { @@ -176,7 +187,7 @@ constructor( ) RINGER_MODE_NORMAL -> when { - isMuted && isEnabled -> + isMuted && !isZenMuted -> RingerButtonViewModel( imageResId = if (isSelectedButton) { @@ -226,7 +237,7 @@ constructor( private fun maybeShowToast(ringerMode: RingerMode) { coroutineScope.launch { - val seenToastCount = interactor.getToastCount() + val seenToastCount = ringerInteractor.getToastCount() if (seenToastCount > SHOW_RINGER_TOAST_COUNT) { return@launch } @@ -260,7 +271,7 @@ constructor( ) } toastText?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT).show() } - interactor.updateToastCount(seenToastCount) + ringerInteractor.updateToastCount(seenToastCount) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index e52bad9c39bf..f30524638150 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -18,11 +18,12 @@ package com.android.systemui.volume.dialog.sliders.ui import android.animation.Animator import android.animation.ObjectAnimator +import android.annotation.SuppressLint import android.view.View import android.view.animation.DecelerateInterpolator import com.android.systemui.res.R -import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope +import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory import com.android.systemui.volume.dialog.ui.utils.awaitAnimation @@ -48,24 +49,27 @@ constructor( val sliderView: Slider = view.requireViewById<Slider>(R.id.volume_dialog_slider).apply { labelBehavior = LabelFormatter.LABEL_GONE + trackIconActiveColor = trackInactiveTintList } sliderView.addOnChangeListener { _, value, fromUser -> viewModel.setStreamVolume(value.roundToInt(), fromUser) } - viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this) + viewModel.state.onEach { it.bindToSlider(sliderView) }.launchIn(this) } - private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) { + @SuppressLint("UseCompatLoadingForDrawables") + private suspend fun VolumeDialogSliderStateModel.bindToSlider(slider: Slider) { with(slider) { - valueFrom = levelMin.toFloat() - valueTo = levelMax.toFloat() + valueFrom = minValue + valueTo = maxValue // coerce the current value to the new value range before animating it value = value.coerceIn(valueFrom, valueTo) setValueAnimated( - level.toFloat(), + value, jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS), ) + trackIconActiveEnd = context.getDrawable(iconRes) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt new file mode 100644 index 000000000000..5c39b6f9359c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.ui.viewmodel + +import android.media.AudioManager +import androidx.annotation.DrawableRes +import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor +import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.settingslib.volume.shared.model.RingerMode +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf + +class VolumeDialogSliderIconProvider +@Inject +constructor( + private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor, + private val audioVolumeInteractor: AudioVolumeInteractor, +) { + + @DrawableRes + fun getStreamIcon( + stream: Int, + level: Int, + levelMin: Int, + levelMax: Int, + isMuted: Boolean, + isRoutedToBluetooth: Boolean, + ): Flow<Int> { + return combine( + notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)), + ringerModeForStream(stream), + ) { isZenMuted, ringerMode -> + val isStreamOffline = level == 0 || isMuted + if (isZenMuted) { + // TODO(b/372466264) use icon for the corresponding zenmode + return@combine com.android.internal.R.drawable.ic_qs_dnd + } + when (ringerMode?.value) { + AudioManager.RINGER_MODE_VIBRATE -> + return@combine R.drawable.ic_volume_ringer_vibrate + AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off + } + if (isRoutedToBluetooth) { + return@combine if (stream == AudioManager.STREAM_VOICE_CALL) { + R.drawable.ic_volume_bt_sco + } else { + if (isStreamOffline) { + R.drawable.ic_volume_media_bt_mute + } else { + R.drawable.ic_volume_media_bt + } + } + } + + return@combine if (isStreamOffline) { + getMutedIconForStream(stream) ?: getIconForStream(stream) + } else { + if (level < (levelMax + levelMin) / 2) { + // This icon is different on TV + R.drawable.ic_volume_media_low + } else { + getIconForStream(stream) + } + } + } + } + + @DrawableRes + private fun getMutedIconForStream(stream: Int): Int? { + return when (stream) { + AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute + AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute + AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute + AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute + else -> null + } + } + + @DrawableRes + private fun getIconForStream(stream: Int): Int { + return when (stream) { + AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility + AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media + AudioManager.STREAM_RING -> R.drawable.ic_ring_volume + AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer + AudioManager.STREAM_ALARM -> R.drawable.ic_alarm + AudioManager.STREAM_VOICE_CALL -> com.android.internal.R.drawable.ic_phone + AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system + else -> error("Unsupported stream: $stream") + } + } + + /** + * Emits [RingerMode] for the [stream] if it's affecting it and null when [RingerMode] doesn't + * affect the [stream] + */ + private fun ringerModeForStream(stream: Int): Flow<RingerMode?> { + return if (stream == AudioManager.STREAM_RING) { + audioVolumeInteractor.ringerMode + } else { + flowOf(null) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt new file mode 100644 index 000000000000..5750c049082f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.sliders.ui.viewmodel + +import androidx.annotation.DrawableRes +import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel + +data class VolumeDialogSliderStateModel( + val minValue: Float, + val maxValue: Float, + val value: Float, + @DrawableRes val iconRes: Int, +) + +fun VolumeDialogStreamModel.toStateModel(@DrawableRes iconRes: Int): VolumeDialogSliderStateModel { + return VolumeDialogSliderStateModel( + minValue = levelMin.toFloat(), + value = level.toFloat(), + maxValue = levelMax.toFloat(), + iconRes = iconRes, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index 6dd5b638a3bc..2d5652420ec8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -32,7 +32,9 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn @@ -56,12 +58,12 @@ constructor( private val interactor: VolumeDialogSliderInteractor, private val visibilityInteractor: VolumeDialogVisibilityInteractor, @VolumeDialog private val coroutineScope: CoroutineScope, + private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider, private val systemClock: SystemClock, ) { private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null) - - val model: Flow<VolumeDialogStreamModel> = + private val model: Flow<VolumeDialogStreamModel> = interactor.slider .filter { val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0 @@ -70,6 +72,21 @@ constructor( .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() + val state: Flow<VolumeDialogSliderStateModel> = + model.flatMapLatest { streamModel -> + with(streamModel) { + volumeDialogSliderIconProvider.getStreamIcon( + stream = stream, + level = level, + levelMin = levelMin, + levelMax = levelMax, + isMuted = muted, + isRoutedToBluetooth = routedToBluetooth, + ) + } + .map { icon -> streamModel.toStateModel(icon) } + } + init { userVolumeUpdates .filterNotNull() diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 073781e6101d..0ec71c21985e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -48,7 +48,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.CoreStartable; import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -60,6 +59,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.wm.shell.dagger.WMComponent; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.onehanded.OneHanded; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index f347d48c49cf..a41725f754df 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -529,6 +529,8 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForDnd_onDndChange_updatesClockZenMode() = testScope.runTest { underTest.listenForDnd(testScope.backgroundScope) + runCurrent() + clearInvocations(events) zenModeRepository.activateMode(dndModeId) runCurrent() @@ -537,7 +539,6 @@ class ClockEventControllerTest : SysuiTestCase() { .onZenDataChanged( eq(ZenData(ZenMode.IMPORTANT_INTERRUPTIONS, R.string::dnd_is_on.name)) ) - clearInvocations(events) zenModeRepository.deactivateMode(dndModeId) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 7c0892891707..11199cb2f34a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -44,6 +44,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.content.Context; import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.AudioRecordingConfiguration; @@ -593,11 +594,11 @@ public class AppOpsControllerTest extends SysuiTestCase { //untrusted receiver access mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, - TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true, AppOpsManager.ATTRIBUTION_FLAG_RECEIVER, TEST_CHAIN_ID); //untrusted intermediary access mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, - TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true, AppOpsManager.ATTRIBUTION_FLAG_INTERMEDIARY, TEST_CHAIN_ID); assertTrue(mController.getActiveAppOps().isEmpty()); } @@ -606,11 +607,11 @@ public class AppOpsControllerTest extends SysuiTestCase { public void testTrustedChainUsagesKept() { //untrusted accessor access mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, - TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true, AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR, TEST_CHAIN_ID); //trusted access mController.onOpActiveChanged(AppOpsManager.OPSTR_CAMERA, TEST_UID, - TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, true, + TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, Context.DEVICE_ID_DEFAULT, true, AppOpsManager.ATTRIBUTION_FLAG_RECEIVER | AppOpsManager.ATTRIBUTION_FLAG_TRUSTED, TEST_CHAIN_ID); assertEquals(2, mController.getActiveAppOps().size()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt new file mode 100644 index 000000000000..d7fcb6a4c2a7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt @@ -0,0 +1,471 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.education.domain.interactor + +import android.content.pm.UserInfo +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS +import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.contextualeducation.GestureType.HOME +import com.android.systemui.contextualeducation.GestureType.OVERVIEW +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.education.data.model.GestureEduModel +import com.android.systemui.education.data.repository.contextualEducationRepository +import com.android.systemui.education.data.repository.fakeEduClock +import com.android.systemui.education.shared.model.EducationUiType +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType +import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository +import com.android.systemui.keyboard.data.repository.keyboardRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.testKosmos +import com.android.systemui.touchpad.data.repository.touchpadRepository +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assume.assumeTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.verify +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +@kotlinx.coroutines.ExperimentalCoroutinesApi +class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: GestureType) : + SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val contextualEduInteractor = kosmos.contextualEducationInteractor + private val repository = kosmos.contextualEducationRepository + private val touchpadRepository = kosmos.touchpadRepository + private val keyboardRepository = kosmos.keyboardRepository + private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository + private val userRepository = kosmos.fakeUserRepository + private val overviewProxyService = kosmos.mockOverviewProxyService + + private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor + private val eduClock = kosmos.fakeEduClock + private val minDurationForNextEdu = + KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds + private val initialDelayElapsedDuration = + KeyboardTouchpadEduInteractor.initialDelayDuration + 1.seconds + + @Before + fun setup() { + underTest.start() + contextualEduInteractor.start() + userRepository.setUserInfos(USER_INFOS) + testScope.launch { + contextualEduInteractor.updateKeyboardFirstConnectionTime() + contextualEduInteractor.updateTouchpadFirstConnectionTime() + } + } + + @Test + fun newEducationInfoOnMaxSignalCountReached() = + testScope.runTest { + triggerMaxEducationSignals(gestureType) + val model by collectLastValue(underTest.educationTriggered) + + assertThat(model?.gestureType).isEqualTo(gestureType) + } + + @Test + fun newEducationToastOn1stEducation() = + testScope.runTest { + val model by collectLastValue(underTest.educationTriggered) + triggerMaxEducationSignals(gestureType) + + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast) + } + + @Test + fun newEducationNotificationOn2ndEducation() = + testScope.runTest { + val model by collectLastValue(underTest.educationTriggered) + triggerMaxEducationSignals(gestureType) + // runCurrent() to trigger 1st education + runCurrent() + + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(gestureType) + + assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) + } + + @Test + fun noEducationInfoBeforeMaxSignalCountReached() = + testScope.runTest { + contextualEduInteractor.incrementSignalCount(gestureType) + val model by collectLastValue(underTest.educationTriggered) + assertThat(model).isNull() + } + + @Test + fun noEducationInfoWhenShortcutTriggeredPreviously() = + testScope.runTest { + val model by collectLastValue(underTest.educationTriggered) + contextualEduInteractor.updateShortcutTriggerTime(gestureType) + triggerMaxEducationSignals(gestureType) + assertThat(model).isNull() + } + + @Test + fun no2ndEducationBeforeMinEduIntervalReached() = + testScope.runTest { + val models by collectValues(underTest.educationTriggered) + triggerMaxEducationSignals(gestureType) + runCurrent() + + // Offset a duration that is less than the required education interval + eduClock.offset(1.seconds) + triggerMaxEducationSignals(gestureType) + runCurrent() + + assertThat(models.filterNotNull().size).isEqualTo(1) + } + + @Test + fun noNewEducationInfoAfterMaxEducationCountReached() = + testScope.runTest { + val models by collectValues(underTest.educationTriggered) + // Trigger 2 educations + triggerMaxEducationSignals(gestureType) + runCurrent() + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(gestureType) + runCurrent() + + // Try triggering 3rd education + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(gestureType) + + assertThat(models.filterNotNull().size).isEqualTo(2) + } + + @Test + fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = + testScope.runTest { + val model by + collectLastValue( + kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType) + ) + contextualEduInteractor.incrementSignalCount(gestureType) + eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds)) + val secondSignalReceivedTime = eduClock.instant() + contextualEduInteractor.incrementSignalCount(gestureType) + + assertThat(model) + .isEqualTo( + GestureEduModel( + signalCount = 1, + usageSessionStartTime = secondSignalReceivedTime, + userId = 0, + gestureType = gestureType, + ) + ) + } + + @Test + fun newTouchpadConnectionTimeOnFirstTouchpadConnected() = + testScope.runTest { + setIsAnyTouchpadConnected(true) + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant()) + } + + @Test + fun unchangedTouchpadConnectionTimeOnSecondConnection() = + testScope.runTest { + val firstConnectionTime = eduClock.instant() + setIsAnyTouchpadConnected(true) + setIsAnyTouchpadConnected(false) + + eduClock.offset(1.hours) + setIsAnyTouchpadConnected(true) + + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime) + } + + @Test + fun newTouchpadConnectionTimeOnUserChanged() = + testScope.runTest { + // Touchpad connected for user 0 + setIsAnyTouchpadConnected(true) + + // Change user + eduClock.offset(1.hours) + val newUserFirstConnectionTime = eduClock.instant() + userRepository.setSelectedUserInfo(USER_INFOS[0]) + runCurrent() + + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime) + } + + @Test + fun newKeyboardConnectionTimeOnKeyboardConnected() = + testScope.runTest { + setIsAnyKeyboardConnected(true) + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant()) + } + + @Test + fun unchangedKeyboardConnectionTimeOnSecondConnection() = + testScope.runTest { + val firstConnectionTime = eduClock.instant() + setIsAnyKeyboardConnected(true) + setIsAnyKeyboardConnected(false) + + eduClock.offset(1.hours) + setIsAnyKeyboardConnected(true) + + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime) + } + + @Test + fun newKeyboardConnectionTimeOnUserChanged() = + testScope.runTest { + // Keyboard connected for user 0 + setIsAnyKeyboardConnected(true) + + // Change user + eduClock.offset(1.hours) + val newUserFirstConnectionTime = eduClock.instant() + userRepository.setSelectedUserInfo(USER_INFOS[0]) + runCurrent() + + val model = contextualEduInteractor.getEduDeviceConnectionTime() + assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime) + } + + @Test + fun updateShortcutTimeOnKeyboardShortcutTriggered() = + testScope.runTest { + // Only All Apps needs to update the keyboard shortcut + assumeTrue(gestureType == ALL_APPS) + kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS) + + val model by + collectLastValue( + kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS) + ) + assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant()) + } + + @Test + fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() = + testScope.runTest { + assumeTrue(gestureType != ALL_APPS) + setUpForInitialDelayElapse() + touchpadRepository.setIsAnyTouchpadConnected(true) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue + 1) + } + + @Test + fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() = + testScope.runTest { + setUpForInitialDelayElapse() + touchpadRepository.setIsAnyTouchpadConnected(false) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + @Test + fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() = + testScope.runTest { + assumeTrue(gestureType == ALL_APPS) + setUpForInitialDelayElapse() + keyboardRepository.setIsAnyKeyboardConnected(true) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue + 1) + } + + @Test + fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() = + testScope.runTest { + setUpForInitialDelayElapse() + keyboardRepository.setIsAnyKeyboardConnected(false) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + @Test + fun dataAddedOnUpdateShortcutTriggerTime() = + testScope.runTest { + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + assertThat(model?.lastShortcutTriggeredTime).isNull() + + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType) + + assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant()) + } + + @Test + fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() = + testScope.runTest { + setUpForDeviceConnection() + tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant()) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + eduClock.offset(initialDelayElapsedDuration) + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue + 1) + } + + @Test + fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() = + testScope.runTest { + setUpForDeviceConnection() + tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant()) + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + // No offset to the clock to simulate update before initial delay + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + @Test + fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() = + testScope.runTest { + // No update to OOBE launch time to simulate no OOBE is launched yet + setUpForDeviceConnection() + + val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) + val originalValue = model!!.signalCount + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + private suspend fun setUpForInitialDelayElapse() { + tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant()) + tutorialSchedulerRepository.updateLaunchTime(DeviceType.KEYBOARD, eduClock.instant()) + eduClock.offset(initialDelayElapsedDuration) + } + + fun logMetricsForToastEducation() = + testScope.runTest { + triggerMaxEducationSignals(gestureType) + runCurrent() + + verify(kosmos.mockEduMetricsLogger) + .logContextualEducationTriggered(gestureType, EducationUiType.Toast) + } + + @Test + fun logMetricsForNotificationEducation() = + testScope.runTest { + triggerMaxEducationSignals(gestureType) + runCurrent() + + eduClock.offset(minDurationForNextEdu) + triggerMaxEducationSignals(gestureType) + runCurrent() + + verify(kosmos.mockEduMetricsLogger) + .logContextualEducationTriggered(gestureType, EducationUiType.Notification) + } + + @After + fun clear() { + testScope.launch { tutorialSchedulerRepository.clear() } + } + + private suspend fun triggerMaxEducationSignals(gestureType: GestureType) { + // Increment max number of signal to try triggering education + for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { + contextualEduInteractor.incrementSignalCount(gestureType) + } + } + + private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) { + touchpadRepository.setIsAnyTouchpadConnected(isConnected) + runCurrent() + } + + private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) { + keyboardRepository.setIsAnyKeyboardConnected(isConnected) + runCurrent() + } + + private fun setUpForDeviceConnection() { + touchpadRepository.setIsAnyTouchpadConnected(true) + keyboardRepository.setIsAnyKeyboardConnected(true) + } + + private fun getOverviewProxyListener(): OverviewProxyListener { + val listenerCaptor = argumentCaptor<OverviewProxyListener>() + verify(overviewProxyService).addCallback(listenerCaptor.capture()) + return listenerCaptor.firstValue + } + + companion object { + private val USER_INFOS = listOf(UserInfo(101, "Second User", 0)) + + @JvmStatic + @Parameters(name = "{0}") + fun getGestureTypes(): List<GestureType> { + return listOf(BACK, HOME, OVERVIEW, ALL_APPS) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt index 2a6d29c61890..580f631734e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,17 @@ package com.android.systemui.education.domain.interactor -import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.contextualeducation.GestureType -import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK import com.android.systemui.contextualeducation.GestureType.HOME import com.android.systemui.contextualeducation.GestureType.OVERVIEW import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.education.data.model.GestureEduModel -import com.android.systemui.education.data.repository.contextualEducationRepository import com.android.systemui.education.data.repository.fakeEduClock +import com.android.systemui.education.shared.model.EducationInfo import com.android.systemui.education.shared.model.EducationUiType import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType import com.android.systemui.inputdevice.tutorial.tutorialSchedulerRepository @@ -37,50 +35,42 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.testKosmos import com.android.systemui.touchpad.data.repository.touchpadRepository -import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat -import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.verify -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(ParameterizedAndroidJunit4::class) +@RunWith(AndroidJUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi -class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : SysuiTestCase() { +class KeyboardTouchpadEduInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val contextualEduInteractor = kosmos.contextualEducationInteractor - private val repository = kosmos.contextualEducationRepository private val touchpadRepository = kosmos.touchpadRepository private val keyboardRepository = kosmos.keyboardRepository private val tutorialSchedulerRepository = kosmos.tutorialSchedulerRepository - private val userRepository = kosmos.fakeUserRepository private val overviewProxyService = kosmos.mockOverviewProxyService private val underTest: KeyboardTouchpadEduInteractor = kosmos.keyboardTouchpadEduInteractor private val eduClock = kosmos.fakeEduClock - private val minDurationForNextEdu = - KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds private val initialDelayElapsedDuration = KeyboardTouchpadEduInteractor.initialDelayDuration + 1.seconds + private val minIntervalForEduNotification = + KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds @Before fun setup() { underTest.start() contextualEduInteractor.start() - userRepository.setUserInfos(USER_INFOS) testScope.launch { contextualEduInteractor.updateKeyboardFirstConnectionTime() contextualEduInteractor.updateTouchpadFirstConnectionTime() @@ -88,312 +78,76 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : } @Test - fun newEducationInfoOnMaxSignalCountReached() = - testScope.runTest { - triggerMaxEducationSignals(gestureType) - val model by collectLastValue(underTest.educationTriggered) - - assertThat(model?.gestureType).isEqualTo(gestureType) - } - - @Test - fun newEducationToastOn1stEducation() = - testScope.runTest { - val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(gestureType) - - assertThat(model?.educationUiType).isEqualTo(EducationUiType.Toast) - } - - @Test - fun newEducationNotificationOn2ndEducation() = - testScope.runTest { - val model by collectLastValue(underTest.educationTriggered) - triggerMaxEducationSignals(gestureType) - // runCurrent() to trigger 1st education - runCurrent() - - eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(gestureType) - - assertThat(model?.educationUiType).isEqualTo(EducationUiType.Notification) - } - - @Test - fun noEducationInfoBeforeMaxSignalCountReached() = - testScope.runTest { - contextualEduInteractor.incrementSignalCount(gestureType) - val model by collectLastValue(underTest.educationTriggered) - assertThat(model).isNull() - } - - @Test - fun noEducationInfoWhenShortcutTriggeredPreviously() = - testScope.runTest { - val model by collectLastValue(underTest.educationTriggered) - contextualEduInteractor.updateShortcutTriggerTime(gestureType) - triggerMaxEducationSignals(gestureType) - assertThat(model).isNull() - } - - @Test - fun no2ndEducationBeforeMinEduIntervalReached() = - testScope.runTest { - val models by collectValues(underTest.educationTriggered) - triggerMaxEducationSignals(gestureType) - runCurrent() - - // Offset a duration that is less than the required education interval - eduClock.offset(1.seconds) - triggerMaxEducationSignals(gestureType) - runCurrent() - - assertThat(models.filterNotNull().size).isEqualTo(1) - } - - @Test - fun noNewEducationInfoAfterMaxEducationCountReached() = - testScope.runTest { - val models by collectValues(underTest.educationTriggered) - // Trigger 2 educations - triggerMaxEducationSignals(gestureType) - runCurrent() - eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(gestureType) - runCurrent() - - // Try triggering 3rd education - eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(gestureType) - - assertThat(models.filterNotNull().size).isEqualTo(2) - } - - @Test - fun startNewUsageSessionWhen2ndSignalReceivedAfterSessionDeadline() = - testScope.runTest { - val model by - collectLastValue( - kosmos.contextualEducationRepository.readGestureEduModelFlow(gestureType) - ) - contextualEduInteractor.incrementSignalCount(gestureType) - eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration.plus(1.seconds)) - val secondSignalReceivedTime = eduClock.instant() - contextualEduInteractor.incrementSignalCount(gestureType) - - assertThat(model) - .isEqualTo( - GestureEduModel( - signalCount = 1, - usageSessionStartTime = secondSignalReceivedTime, - userId = 0, - gestureType = gestureType, - ) - ) - } - - @Test - fun newTouchpadConnectionTimeOnFirstTouchpadConnected() = - testScope.runTest { - setIsAnyTouchpadConnected(true) - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.touchpadFirstConnectionTime).isEqualTo(eduClock.instant()) - } - - @Test - fun unchangedTouchpadConnectionTimeOnSecondConnection() = - testScope.runTest { - val firstConnectionTime = eduClock.instant() - setIsAnyTouchpadConnected(true) - setIsAnyTouchpadConnected(false) - - eduClock.offset(1.hours) - setIsAnyTouchpadConnected(true) - - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.touchpadFirstConnectionTime).isEqualTo(firstConnectionTime) - } - - @Test - fun newTouchpadConnectionTimeOnUserChanged() = - testScope.runTest { - // Touchpad connected for user 0 - setIsAnyTouchpadConnected(true) - - // Change user - eduClock.offset(1.hours) - val newUserFirstConnectionTime = eduClock.instant() - userRepository.setSelectedUserInfo(USER_INFOS[0]) - runCurrent() - - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.touchpadFirstConnectionTime).isEqualTo(newUserFirstConnectionTime) - } - - @Test - fun newKeyboardConnectionTimeOnKeyboardConnected() = - testScope.runTest { - setIsAnyKeyboardConnected(true) - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.keyboardFirstConnectionTime).isEqualTo(eduClock.instant()) - } - - @Test - fun unchangedKeyboardConnectionTimeOnSecondConnection() = - testScope.runTest { - val firstConnectionTime = eduClock.instant() - setIsAnyKeyboardConnected(true) - setIsAnyKeyboardConnected(false) - - eduClock.offset(1.hours) - setIsAnyKeyboardConnected(true) - - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.keyboardFirstConnectionTime).isEqualTo(firstConnectionTime) - } - - @Test - fun newKeyboardConnectionTimeOnUserChanged() = - testScope.runTest { - // Keyboard connected for user 0 - setIsAnyKeyboardConnected(true) - - // Change user - eduClock.offset(1.hours) - val newUserFirstConnectionTime = eduClock.instant() - userRepository.setSelectedUserInfo(USER_INFOS[0]) - runCurrent() - - val model = contextualEduInteractor.getEduDeviceConnectionTime() - assertThat(model.keyboardFirstConnectionTime).isEqualTo(newUserFirstConnectionTime) - } - - @Test - fun updateShortcutTimeOnKeyboardShortcutTriggered() = - testScope.runTest { - // Only All Apps needs to update the keyboard shortcut - assumeTrue(gestureType == ALL_APPS) - kosmos.contextualEducationRepository.setKeyboardShortcutTriggered(ALL_APPS) - - val model by - collectLastValue( - kosmos.contextualEducationRepository.readGestureEduModelFlow(ALL_APPS) - ) - assertThat(model?.lastShortcutTriggeredTime).isEqualTo(eduClock.instant()) - } - - @Test - fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() = - testScope.runTest { - assumeTrue(gestureType != ALL_APPS) - setUpForInitialDelayElapse() - touchpadRepository.setIsAnyTouchpadConnected(true) - - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) - - assertThat(model?.signalCount).isEqualTo(originalValue + 1) - } - - @Test - fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() = - testScope.runTest { - setUpForInitialDelayElapse() - touchpadRepository.setIsAnyTouchpadConnected(false) - - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) - - assertThat(model?.signalCount).isEqualTo(originalValue) - } - - @Test - fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() = - testScope.runTest { - assumeTrue(gestureType == ALL_APPS) - setUpForInitialDelayElapse() - keyboardRepository.setIsAnyKeyboardConnected(true) - - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) - - assertThat(model?.signalCount).isEqualTo(originalValue + 1) - } - - @Test - fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() = + fun newEducationToastBeforeMaxToastsPerSessionTriggered() = testScope.runTest { + setUpForDeviceConnection() setUpForInitialDelayElapse() - keyboardRepository.setIsAnyKeyboardConnected(false) - - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) - - assertThat(model?.signalCount).isEqualTo(originalValue) - } - - @Test - fun dataAddedOnUpdateShortcutTriggerTime() = - testScope.runTest { - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - assertThat(model?.lastShortcutTriggeredTime).isNull() + val model by collectLastValue(underTest.educationTriggered) - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ true, gestureType) + triggerEducation(HOME) - assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant()) + assertThat(model).isEqualTo(EducationInfo(HOME, EducationUiType.Toast, userId = 0)) } @Test - fun dataUpdatedOnIncrementSignalCountAfterInitialDelay() = + fun noEducationToastAfterMaxToastsPerSessionTriggered() = testScope.runTest { setUpForDeviceConnection() - tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant()) + setUpForInitialDelayElapse() + val models by collectValues(underTest.educationTriggered.filterNotNull()) + // Show two toasts of other gestures + triggerEducation(HOME) + triggerEducation(BACK) - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - eduClock.offset(initialDelayElapsedDuration) - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + triggerEducation(OVERVIEW) - assertThat(model?.signalCount).isEqualTo(originalValue + 1) + // No new toast education besides the 2 triggered at first + val firstEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0) + val secondEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0) + assertThat(models).containsExactly(firstEdu, secondEdu).inOrder() } @Test - fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() = + fun newEducationToastAfterMinIntervalElapsedWhenMaxToastsPerSessionTriggered() = testScope.runTest { setUpForDeviceConnection() - tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant()) + setUpForInitialDelayElapse() + val models by collectValues(underTest.educationTriggered.filterNotNull()) + // Show two toasts of other gestures + triggerEducation(HOME) + triggerEducation(BACK) - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - // No offset to the clock to simulate update before initial delay - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + // Trigger toast after an usage session has elapsed + eduClock.offset(KeyboardTouchpadEduInteractor.usageSessionDuration + 1.seconds) + triggerEducation(OVERVIEW) - assertThat(model?.signalCount).isEqualTo(originalValue) + val firstEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0) + val secondEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0) + val thirdEdu = EducationInfo(OVERVIEW, EducationUiType.Toast, userId = 0) + assertThat(models).containsExactly(firstEdu, secondEdu, thirdEdu).inOrder() } @Test - fun dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() = + fun newEducationNotificationAfterMaxToastsPerSessionTriggered() = testScope.runTest { - // No update to OOBE launch time to simulate no OOBE is launched yet setUpForDeviceConnection() + setUpForInitialDelayElapse() + val models by collectValues(underTest.educationTriggered.filterNotNull()) + triggerEducation(BACK) - val model by collectLastValue(repository.readGestureEduModelFlow(gestureType)) - val originalValue = model!!.signalCount - val listener = getOverviewProxyListener() - listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) + // Offset to let min interval for notification elapse so we could show edu notification + // for BACK. It would be a new usage session too because the interval (7 days) is + // longer than a usage session (3 days) + eduClock.offset(minIntervalForEduNotification) + triggerEducation(HOME) + triggerEducation(OVERVIEW) + triggerEducation(BACK) - assertThat(model?.signalCount).isEqualTo(originalValue) + val firstEdu = EducationInfo(BACK, EducationUiType.Toast, userId = 0) + val secondEdu = EducationInfo(HOME, EducationUiType.Toast, userId = 0) + val thirdEdu = EducationInfo(OVERVIEW, EducationUiType.Toast, userId = 0) + val fourthEdu = EducationInfo(BACK, EducationUiType.Notification, userId = 0) + assertThat(models).containsExactly(firstEdu, secondEdu, thirdEdu, fourthEdu).inOrder() } private suspend fun setUpForInitialDelayElapse() { @@ -402,51 +156,6 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : eduClock.offset(initialDelayElapsedDuration) } - fun logMetricsForToastEducation() = - testScope.runTest { - triggerMaxEducationSignals(gestureType) - runCurrent() - - verify(kosmos.mockEduMetricsLogger) - .logContextualEducationTriggered(gestureType, EducationUiType.Toast) - } - - @Test - fun logMetricsForNotificationEducation() = - testScope.runTest { - triggerMaxEducationSignals(gestureType) - runCurrent() - - eduClock.offset(minDurationForNextEdu) - triggerMaxEducationSignals(gestureType) - runCurrent() - - verify(kosmos.mockEduMetricsLogger) - .logContextualEducationTriggered(gestureType, EducationUiType.Notification) - } - - @After - fun clear() { - testScope.launch { tutorialSchedulerRepository.clear() } - } - - private suspend fun triggerMaxEducationSignals(gestureType: GestureType) { - // Increment max number of signal to try triggering education - for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { - contextualEduInteractor.incrementSignalCount(gestureType) - } - } - - private fun TestScope.setIsAnyTouchpadConnected(isConnected: Boolean) { - touchpadRepository.setIsAnyTouchpadConnected(isConnected) - runCurrent() - } - - private fun TestScope.setIsAnyKeyboardConnected(isConnected: Boolean) { - keyboardRepository.setIsAnyKeyboardConnected(isConnected) - runCurrent() - } - private fun setUpForDeviceConnection() { touchpadRepository.setIsAnyTouchpadConnected(true) keyboardRepository.setIsAnyKeyboardConnected(true) @@ -458,13 +167,12 @@ class KeyboardTouchpadEduInteractorTest(private val gestureType: GestureType) : return listenerCaptor.firstValue } - companion object { - private val USER_INFOS = listOf(UserInfo(101, "Second User", 0)) - - @JvmStatic - @Parameters(name = "{0}") - fun getGestureTypes(): List<GestureType> { - return listOf(BACK, HOME, OVERVIEW, ALL_APPS) + private fun TestScope.triggerEducation(gestureType: GestureType) { + // Increment max number of signal to try triggering education + for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) { + val listener = getOverviewProxyListener() + listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType) } + runCurrent() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index d88d69da5e59..d2317e4f533d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt @@ -22,7 +22,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assert +import androidx.compose.ui.test.filter import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule @@ -182,11 +185,14 @@ class DragAndDropTest : SysuiTestCase() { } private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) { - onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply { - fetchSemanticsNodes().forEachIndexed { index, _ -> - get(index).assert(hasContentDescription(specs[index])) + onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG) + .onChildren() + .filter(SemanticsMatcher.keyIsDefined(SemanticsProperties.ContentDescription)) + .apply { + fetchSemanticsNodes().forEachIndexed { index, _ -> + get(index).assert(hasContentDescription(specs[index])) + } } - } } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 9a924ed5a630..d8d6f2e9fbb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -4,9 +4,10 @@ import android.bluetooth.BluetoothDevice import android.os.Handler import android.os.Looper import android.os.UserManager +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.telephony.flags.Flags @@ -22,8 +23,11 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes import com.android.systemui.res.R import com.android.systemui.statusbar.policy.BluetoothController import com.android.systemui.util.mockito.any @@ -41,11 +45,17 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest -class BluetoothTileTest : SysuiTestCase() { +class BluetoothTileTest(flags: FlagsParameterization) : SysuiTestCase() { + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var qsHost: QSHost @@ -81,7 +91,7 @@ class BluetoothTileTest : SysuiTestCase() { qsLogger, bluetoothController, featureFlags, - bluetoothTileDialogViewModel + bluetoothTileDialogViewModel, ) tile.initialize() @@ -109,8 +119,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -121,8 +130,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_off)) } @Test @@ -133,8 +141,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_on)) } @Test @@ -145,8 +152,7 @@ class BluetoothTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_bluetooth_icon_search)) } @Test @@ -161,11 +167,10 @@ class BluetoothTileTest : SysuiTestCase() { .isEqualTo( mContext.getString( R.string.quick_settings_bluetooth_secondary_label_battery_level, - Utils.formatPercentage(50) + Utils.formatPercentage(50), ) ) - verify(bluetoothController) - .addOnMetadataChangedListener(eq(cachedDevice), any(), any()) + verify(bluetoothController).addOnMetadataChangedListener(eq(cachedDevice), any(), any()) } @Test @@ -186,7 +191,7 @@ class BluetoothTileTest : SysuiTestCase() { .isEqualTo( mContext.getString( R.string.quick_settings_bluetooth_secondary_label_battery_level, - Utils.formatPercentage(25) + Utils.formatPercentage(25), ) ) verify(bluetoothController, times(1)) @@ -197,7 +202,7 @@ class BluetoothTileTest : SysuiTestCase() { fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() { mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) - .thenReturn(false) + .thenReturn(false) `when`(clickJob.isCompleted).thenReturn(false) tile.mClickJob = clickJob @@ -210,7 +215,7 @@ class BluetoothTileTest : SysuiTestCase() { fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() { mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) - .thenReturn(false) + .thenReturn(false) tile.handleClick(null) @@ -221,7 +226,7 @@ class BluetoothTileTest : SysuiTestCase() { fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() { mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) - .thenReturn(true) + .thenReturn(true) tile.handleClick(null) @@ -265,7 +270,7 @@ class BluetoothTileTest : SysuiTestCase() { qsLogger: QSLogger, bluetoothController: BluetoothController, featureFlags: FeatureFlagsClassic, - bluetoothTileDialogViewModel: BluetoothTileDialogViewModel + bluetoothTileDialogViewModel: BluetoothTileDialogViewModel, ) : BluetoothTile( qsHost, @@ -279,13 +284,13 @@ class BluetoothTileTest : SysuiTestCase() { qsLogger, bluetoothController, featureFlags, - bluetoothTileDialogViewModel + bluetoothTileDialogViewModel, ) { var restrictionChecked: String? = null override fun checkIfRestrictionEnforcedByAdminOnly( state: QSTile.State?, - userRestriction: String? + userRestriction: String?, ) { restrictionChecked = userRestriction } @@ -321,7 +326,7 @@ class BluetoothTileTest : SysuiTestCase() { fun listenToDeviceMetadata( state: QSTile.BooleanState, cachedDevice: CachedBluetoothDevice, - batteryLevel: Int + batteryLevel: Int, ) { val btDevice = mock<BluetoothDevice>() whenever(cachedDevice.device).thenReturn(btDevice) @@ -332,4 +337,20 @@ class BluetoothTileTest : SysuiTestCase() { addConnectedDevice(cachedDevice) tile.handleUpdateState(state, /* arg= */ null) } + + private fun createExpectedIcon(resId: Int): QSTile.Icon { + return if (isEnabled) { + DrawableIconWithRes(mContext.getDrawable(resId), resId) + } else { + QSTileImpl.ResourceIcon.get(resId) + } + } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index 6a43a61dad77..55fb6dacfc3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -21,15 +21,15 @@ import android.content.ContextWrapper import android.content.SharedPreferences import android.os.Handler import android.platform.test.annotations.DisableFlags +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import android.provider.Settings import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.TestableLooper import android.view.ContextThemeWrapper -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable @@ -39,14 +39,19 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.flags.QSComposeFragment +import com.android.systemui.qs.flags.QsInCompose.isEnabled import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIconWithRes +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import java.io.File import org.junit.After import org.junit.Before import org.junit.Test @@ -55,56 +60,55 @@ import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations -import java.io.File import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @DisableFlags(android.app.Flags.FLAG_MODES_UI) -class DndTileTest : SysuiTestCase() { +class DndTileTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { private const val DEFAULT_USER = 0 private const val KEY = Settings.Secure.ZEN_DURATION + + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(QSComposeFragment.FLAG_NAME) + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) } - @Mock - private lateinit var qsHost: QSHost + @Mock private lateinit var qsHost: QSHost - @Mock - private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var metricsLogger: MetricsLogger - @Mock - private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var qsLogger: QSLogger + @Mock private lateinit var qsLogger: QSLogger - @Mock - private lateinit var uiEventLogger: QsEventLogger + @Mock private lateinit var uiEventLogger: QsEventLogger - @Mock - private lateinit var zenModeController: ZenModeController + @Mock private lateinit var zenModeController: ZenModeController - @Mock - private lateinit var sharedPreferences: SharedPreferences + @Mock private lateinit var sharedPreferences: SharedPreferences - @Mock - private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator + @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator - @Mock - private lateinit var hostDialog: Dialog + @Mock private lateinit var hostDialog: Dialog - @Mock - private lateinit var expandable: Expandable + @Mock private lateinit var expandable: Expandable - @Mock - private lateinit var controller: DialogTransitionAnimator.Controller + @Mock private lateinit var controller: DialogTransitionAnimator.Controller private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper @@ -118,31 +122,32 @@ class DndTileTest : SysuiTestCase() { whenever(qsHost.userId).thenReturn(DEFAULT_USER) - val wrappedContext = object : ContextWrapper( - ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) - ) { - override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { - return sharedPreferences + val wrappedContext = + object : + ContextWrapper(ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)) { + override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { + return sharedPreferences + } } - } whenever(qsHost.context).thenReturn(wrappedContext) whenever(expandable.dialogTransitionController(any())).thenReturn(controller) - tile = DndTile( - qsHost, - uiEventLogger, - testableLooper.looper, - Handler(testableLooper.looper), - FalsingManagerFake(), - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger, - zenModeController, - sharedPreferences, - secureSettings, - mDialogTransitionAnimator - ) + tile = + DndTile( + qsHost, + uiEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger, + zenModeController, + sharedPreferences, + secureSettings, + mDialogTransitionAnimator, + ) } @After @@ -222,7 +227,7 @@ class DndTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_off)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_dnd_icon_off)) } @Test @@ -232,6 +237,14 @@ class DndTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) - assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_on)) + assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_dnd_icon_on)) + } + + private fun createExpectedIcon(resId: Int): QSTile.Icon { + return if (isEnabled) { + DrawableIconWithRes(mContext.getDrawable(resId), resId) + } else { + QSTileImpl.ResourceIcon.get(resId) + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java index 190d80f9f6c4..5f63b15916a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java @@ -16,6 +16,10 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; @@ -32,12 +36,12 @@ import android.content.Intent; import android.content.pm.UserInfo; import android.os.Handler; import android.os.RemoteException; +import android.platform.test.flag.junit.FlagsParameterization; import android.provider.Settings; import android.service.dreams.IDreamManager; import android.service.quicksettings.Tile; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -45,9 +49,11 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.flags.QsInCompose; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; @@ -63,11 +69,21 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class DreamTileTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private ActivityStarter mActivityStarter; @Mock @@ -101,6 +117,11 @@ public class DreamTileTest extends SysuiTestCase { private final String mExpectedTileLabel = mContext.getResources().getString( R.string.quick_settings_screensaver_label); + public DreamTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -246,13 +267,13 @@ public class DreamTileTest extends SysuiTestCase { dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_DESK); receiver.onReceive(mContext, dockIntent); mTestableLooper.processAllMessages(); - assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver), + assertEquals(createExpectedIcon(R.drawable.ic_qs_screen_saver), dockedTile.getState().icon); dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); receiver.onReceive(mContext, dockIntent); mTestableLooper.processAllMessages(); - assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked), + assertEquals(createExpectedIcon(R.drawable.ic_qs_screen_saver_undocked), dockedTile.getState().icon); destroyTile(dockedTile); @@ -268,6 +289,14 @@ public class DreamTileTest extends SysuiTestCase { mTestableLooper.processAllMessages(); } + private QSTile.Icon createExpectedIcon(int resId) { + if (QsInCompose.isEnabled()) { + return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId); + } else { + return QSTileImpl.ResourceIcon.get(resId); + } + } + private DreamTile constructTileForTest(boolean dreamSupported, boolean dreamOnlyEnabledForSystemUser) { return new DreamTile( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index 5bd6944e863f..ba6c2dc4f705 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -16,16 +16,20 @@ package com.android.systemui.qs.tiles; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.os.Handler; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.Tile; import android.testing.TestableLooper; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -38,6 +42,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.flags.QsInCompose; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; @@ -54,11 +59,21 @@ import org.mockito.MockitoSession; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -@RunWith(AndroidJUnit4.class) +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class HotspotTileTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Rule public MockitoRule mRule = MockitoJUnit.rule(); @Mock @@ -74,6 +89,11 @@ public class HotspotTileTest extends SysuiTestCase { private HotspotTile mTile; private QSTile.BooleanState mState = new QSTile.BooleanState(); + public HotspotTileTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { mTestableLooper = TestableLooper.get(this); @@ -144,7 +164,7 @@ public class HotspotTileTest extends SysuiTestCase { mTile.handleUpdateState(state, /* arg= */ null); assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off)); + .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_off)); } @Test @@ -156,7 +176,7 @@ public class HotspotTileTest extends SysuiTestCase { mTile.handleUpdateState(state, /* arg= */ null); assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search)); + .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_search)); } @Test @@ -168,6 +188,14 @@ public class HotspotTileTest extends SysuiTestCase { mTile.handleUpdateState(state, /* arg= */ null); assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on)); + .isEqualTo(createExpectedIcon(R.drawable.qs_hotspot_icon_on)); + } + + private QSTile.Icon createExpectedIcon(int resId) { + if (QsInCompose.isEnabled()) { + return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId); + } else { + return QSTileImpl.ResourceIcon.get(resId); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 5ada2f3fd63d..b26f0a6e71a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -260,7 +260,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { @Test public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() { - when(mTelephonyManager.isDataEnabled()).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + when(spyController.isMobileDataEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(true); when(mConnectivityManager.getActiveNetwork()).thenReturn(mNetwork); when(mConnectivityManager.getNetworkCapabilities(mNetwork)) @@ -272,7 +273,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now, TOAST_MESSAGE_STRING); - mInternetDialogController.connectCarrierNetwork(); + spyController.connectCarrierNetwork(); verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */); verify(mToastFactory).createToast(any(), any(), eq(TOAST_MESSAGE_STRING), anyString(), @@ -281,11 +282,12 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { @Test public void connectCarrierNetwork_mergedCarrierEntryCanConnect_doNothingWhenSettingsOff() { - when(mTelephonyManager.isDataEnabled()).thenReturn(false); - + InternetDialogController spyController = spy(mInternetDialogController); + when(spyController.isMobileDataEnabled()).thenReturn(false); mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now, TOAST_MESSAGE_STRING); - mInternetDialogController.connectCarrierNetwork(); + + spyController.connectCarrierNetwork(); verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */); verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(), @@ -294,12 +296,13 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { @Test public void connectCarrierNetwork_mergedCarrierEntryCanConnect_doNothingWhenKeyguardLocked() { - when(mTelephonyManager.isDataEnabled()).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + when(spyController.isMobileDataEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(false); mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now, TOAST_MESSAGE_STRING); - mInternetDialogController.connectCarrierNetwork(); + spyController.connectCarrierNetwork(); verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */); verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(), @@ -308,7 +311,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { @Test public void connectCarrierNetwork_mergedCarrierEntryCanConnect_doNothingWhenMobileIsPrimary() { - when(mTelephonyManager.isDataEnabled()).thenReturn(true); + InternetDialogController spyController = spy(mInternetDialogController); + when(spyController.isMobileDataEnabled()).thenReturn(true); when(mKeyguardStateController.isUnlocked()).thenReturn(true); when(mConnectivityManager.getActiveNetwork()).thenReturn(mNetwork); when(mConnectivityManager.getNetworkCapabilities(mNetwork)) @@ -446,10 +450,9 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); spyController.onAccessPointsChanged(null /* accessPoints */); - doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId(); + doReturn(SUB_ID).when(spyController).getActiveAutoSwitchNonDdsSubId(); doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); - doReturn(mServiceState).when(mTelephonyManager).getServiceState(); - doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState(); + spyController.mSubIdServiceState.put(SUB_ID2, mServiceState); assertFalse(TextUtils.equals(spyController.getSubtitleText(false), getResourcesString("all_network_unavailable"))); @@ -469,8 +472,7 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { spyController.onAccessPointsChanged(null /* accessPoints */); doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState(); - doReturn(mServiceState).when(mTelephonyManager).getServiceState(); - doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState(); + spyController.mSubIdServiceState.put(SUB_ID, mServiceState); assertTrue(TextUtils.equals(spyController.getSubtitleText(false), getResourcesString("all_network_unavailable"))); @@ -487,11 +489,10 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { fakeAirplaneModeEnabled(false); when(mWifiStateWorker.isWifiEnabled()).thenReturn(true); mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); + InternetDialogController spyController = spy(mInternetDialogController); doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState(); - doReturn(mServiceState).when(mTelephonyManager).getServiceState(); - - when(mTelephonyManager.isDataEnabled()).thenReturn(false); + spyController.mSubIdServiceState.put(SUB_ID, mServiceState); assertThat(mInternetDialogController.getSubtitleText(false)) .isEqualTo(getResourcesString("non_carrier_network_unavailable")); @@ -499,6 +500,9 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { // if the Wi-Fi disallow config, then don't return Wi-Fi related string. mInternetDialogController.mCanConfigWifi = false; + when(spyController.isMobileDataEnabled()).thenReturn(false); + + assertThat(mInternetDialogController.getSubtitleText(false)) .isNotEqualTo(getResourcesString("non_carrier_network_unavailable")); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index e7fb470cfa76..c410111bc2e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -33,7 +33,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE +import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.ambient.touch.TouchHandler @@ -43,7 +43,9 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable +import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel @@ -131,20 +133,26 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { underTest = GlanceableHubContainerController( communalInteractor, + communalSettingsInteractor, communalViewModel, keyguardInteractor, - kosmos.keyguardTransitionInteractor, + keyguardTransitionInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, communalContent, - kosmos.sceneDataSourceDelegator, - kosmos.notificationStackScrollLayoutController, - kosmos.keyguardMediaController, - kosmos.lockscreenSmartspaceController, + sceneDataSourceDelegator, + notificationStackScrollLayoutController, + keyguardMediaController, + lockscreenSmartspaceController, logcatLogBuffer("GlanceableHubContainerControllerTest"), ) + + // Make below last notification true by default or else touches will be ignored by + // default when the hub is not showing. + whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any())) + .thenReturn(true) } testableLooper = TestableLooper.get(this) @@ -178,6 +186,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { underTest = GlanceableHubContainerController( communalInteractor, + kosmos.communalSettingsInteractor, communalViewModel, keyguardInteractor, kosmos.keyguardTransitionInteractor, @@ -207,6 +216,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { val underTest = GlanceableHubContainerController( communalInteractor, + kosmos.communalSettingsInteractor, communalViewModel, keyguardInteractor, kosmos.keyguardTransitionInteractor, @@ -231,6 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { val underTest = GlanceableHubContainerController( communalInteractor, + kosmos.communalSettingsInteractor, communalViewModel, keyguardInteractor, kosmos.keyguardTransitionInteractor, @@ -631,7 +642,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } - @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test fun onTouchEvent_shadeInteracting_movesNotDispatched() = with(kosmos) { @@ -688,7 +699,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } - @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE) + @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test fun onTouchEvent_bouncerInteracting_movesNotDispatched() = with(kosmos) { @@ -721,11 +732,13 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } - @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE) + @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @Test fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() = with(kosmos) { testScope.runTest { + kosmos.setCommunalV2ConfigEnabled(true) + // On lockscreen. goToScene(CommunalScenes.Blank) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 6912eda3c3d4..d157cf2d51e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -36,8 +36,6 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_K import static com.google.common.truth.Truth.assertThat; -import static kotlinx.coroutines.flow.FlowKt.flowOf; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -56,6 +54,8 @@ import static org.mockito.Mockito.when; import static java.util.Collections.emptySet; +import static kotlinx.coroutines.flow.FlowKt.flowOf; + import android.app.ActivityManager; import android.app.IWallpaperManager; import android.app.NotificationManager; @@ -138,6 +138,7 @@ import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; import com.android.systemui.scene.domain.startable.ScrimStartable; @@ -181,6 +182,7 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.interruption.AvalancheProvider; import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; @@ -198,7 +200,6 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.window.StatusBarWindowController; @@ -217,10 +218,6 @@ import com.android.systemui.volume.VolumeComponent; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.startingsurface.StartingSurface; -import dagger.Lazy; - -import kotlinx.coroutines.test.TestScope; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -237,6 +234,9 @@ import java.util.Set; import javax.inject.Provider; +import dagger.Lazy; +import kotlinx.coroutines.test.TestScope; + @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @@ -667,6 +667,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.startKeyguard(); mInitController.executePostInitTasks(); mCentralSurfaces.registerCallbacks(); + // Clear first invocations caused by registering flows with JavaAdapter + mTestScope.getTestScheduler().runCurrent(); + clearInvocations(mScrimController); } @Test @@ -1159,8 +1162,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test @EnableSceneContainer - public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() { - mCentralSurfaces.registerCallbacks(); + public void brightnesShowingChanged_sceneContainerFlagEnabled_ScrimControllerNotified() { final ScrimStartable scrimStartable = mKosmos.getScrimStartable(); scrimStartable.start(); @@ -1178,9 +1180,25 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test @DisableSceneContainer - public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() { - mCentralSurfaces.registerCallbacks(); + @EnableFlags(QSComposeFragment.FLAG_NAME) + public void brightnesShowingChanged_qsUiRefactorFlagEnabled_ScrimControllerNotified() { + mBrightnessMirrorShowingInteractor.setMirrorShowing(true); + mTestScope.getTestScheduler().runCurrent(); + verify(mScrimController, atLeastOnce()).legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR); + clearInvocations(mScrimController); + + mBrightnessMirrorShowingInteractor.setMirrorShowing(false); + mTestScope.getTestScheduler().runCurrent(); + ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class); + // The default is to call the one with the callback argument + verify(mScrimController, atLeastOnce()).legacyTransitionTo(captor.capture(), any()); + assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR); + } + @Test + @DisableSceneContainer + @DisableFlags(QSComposeFragment.FLAG_NAME) + public void brightnesShowingChanged_flagsDisabled_ScrimControllerNotified() { mBrightnessMirrorShowingInteractor.setMirrorShowing(true); mTestScope.getTestScheduler().runCurrent(); verify(mScrimController, never()).legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 1f68195a9acc..ad92b318b0b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.domain.interactor +import android.content.testableContext import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.communalMediaRepository @@ -34,6 +35,7 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter +import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.fakeManagedProfileController @@ -67,6 +69,13 @@ val Kosmos.communalInteractor by Fixture { val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() } +fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) { + testableContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + enabled, + ) +} + suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) { fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled) if (enabled) { @@ -76,6 +85,15 @@ suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) { } } +suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) { + setCommunalV2ConfigEnabled(true) + if (enabled) { + fakeUserRepository.asMainUser() + } else { + fakeUserRepository.asDefaultUser() + } +} + suspend fun Kosmos.setCommunalAvailable(available: Boolean) { setCommunalEnabled(available) with(fakeKeyguardRepository) { @@ -83,3 +101,12 @@ suspend fun Kosmos.setCommunalAvailable(available: Boolean) { setKeyguardShowing(available) } } + +suspend fun Kosmos.setCommunalV2Available(available: Boolean) { + setCommunalV2ConfigEnabled(true) + setCommunalEnabled(available) + with(fakeKeyguardRepository) { + setIsEncryptedOrLockdown(!available) + setKeyguardShowing(available) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt new file mode 100644 index 000000000000..b407b1ba227a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalToDreamButtonViewModelKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import android.service.dream.dreamManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.statusbar.policy.batteryController + +val Kosmos.communalToDreamButtonViewModel by + Kosmos.Fixture { + CommunalToDreamButtonViewModel( + backgroundContext = testDispatcher, + batteryController = batteryController, + dreamManager = dreamManager, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt index 1df3ef48d5a7..1021169c4b3b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt @@ -17,9 +17,11 @@ package com.android.systemui.education.data.repository import com.android.systemui.kosmos.Kosmos +import java.time.Duration import java.time.Instant var Kosmos.contextualEducationRepository: FakeContextualEducationRepository by Kosmos.Fixture { FakeContextualEducationRepository() } -var Kosmos.fakeEduClock: FakeEduClock by Kosmos.Fixture { FakeEduClock(Instant.MIN) } +var Kosmos.fakeEduClock: FakeEduClock by + Kosmos.Fixture { FakeEduClock(Instant.ofEpochSecond(Duration.ofDays(30).seconds)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt new file mode 100644 index 000000000000..63bfa52e9720 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.globalactions + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +/** Provides a mock */ +val Kosmos.globalActionsDialogLite: GlobalActionsDialogLite by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index 552cd9488657..4cb8a416124f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository import com.android.systemui.keyboard.shortcut.data.repository.CustomShortcutCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.DefaultShortcutCategoriesRepository +import com.android.systemui.keyboard.shortcut.data.repository.InputGestureDataAdapter import com.android.systemui.keyboard.shortcut.data.repository.InputGestureMaps import com.android.systemui.keyboard.shortcut.data.repository.ShortcutCategoriesUtils import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository @@ -112,6 +113,8 @@ val Kosmos.defaultShortcutCategoriesRepository by val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) } +val Kosmos.inputGestureDataAdapter by Kosmos.Fixture { InputGestureDataAdapter(userTracker, inputGestureMaps, applicationContext)} + val Kosmos.customInputGesturesRepository by Kosmos.Fixture { CustomInputGesturesRepository(userTracker, testDispatcher) } @@ -122,8 +125,7 @@ val Kosmos.customShortcutCategoriesRepository by applicationCoroutineScope, testDispatcher, shortcutCategoriesUtils, - applicationContext, - inputGestureMaps, + inputGestureDataAdapter, customInputGesturesRepository, fakeInputManager.inputManager, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt index 10b073e8f331..2e6d8ed5aa5b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt @@ -28,6 +28,7 @@ import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.FakeStatusBarStateController import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.mockito.mock @@ -49,3 +50,6 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by { alternateBouncerInteractor }, ) } + +var Kosmos.fakeStatusBarStateController: SysuiStatusBarStateController by + Kosmos.Fixture { FakeStatusBarStateController() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt index c574463eb258..d71bc310b0ed 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt @@ -33,6 +33,7 @@ import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteracto import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel import com.android.systemui.qs.panels.ui.viewmodel.mediaInRowInLandscapeViewModelFactory import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.largeScreenHeaderHelper import com.android.systemui.shade.transition.largeScreenShadeInterpolator import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor @@ -56,6 +57,7 @@ val Kosmos.qsFragmentComposeViewModelFactory by disableFlagsInteractor, keyguardTransitionInteractor, largeScreenShadeInterpolator, + shadeInteractor, configurationInteractor, largeScreenHeaderHelper, tileSquishinessInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt index 4acedaa9044d..322b412fb054 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt @@ -18,5 +18,4 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by - Kosmos.Fixture { GridLayoutTypeRepositoryImpl() } +var Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt index c9516429553b..40c3c96ff241 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt @@ -20,10 +20,19 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout +import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.paginatedGridLayout +import com.android.systemui.shade.domain.interactor.shadeModeInteractor val Kosmos.gridLayoutTypeInteractor by - Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) } + Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository, shadeModeInteractor) } val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by - Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) } + Kosmos.Fixture { + mapOf( + Pair(InfiniteGridLayoutType, infiniteGridLayout), + Pair(PaginatedGridLayoutType, paginatedGridLayout), + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt new file mode 100644 index 000000000000..f412d98a9d4f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout +import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModelFactory + +val Kosmos.paginatedGridLayout by + Kosmos.Fixture { PaginatedGridLayout(paginatedGridViewModelFactory, infiniteGridLayout) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt index 6fe860cfd0d3..f57806c8b678 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt @@ -14,11 +14,10 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.domain.interactor +package com.android.systemui.qs.panels.ui.compose.infinitegrid import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider import com.android.systemui.kosmos.Kosmos -import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt index 33227a4fcc62..86c3add09577 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt @@ -23,8 +23,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout import com.android.systemui.qs.panels.domain.interactor.tilesAvailabilityInteractor +import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt index 5c71ba2f8bbd..128cfcad5c45 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory val Kosmos.paginatedGridViewModel by Kosmos.Fixture { @@ -33,3 +34,10 @@ val Kosmos.paginatedGridViewModel by falsingInteractor, ) } + +val Kosmos.paginatedGridViewModelFactory by + Kosmos.Fixture { + object : PaginatedGridViewModel.Factory { + override fun create() = paginatedGridViewModel + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt index 9481fcac97d6..e79d213a8ffb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt @@ -20,7 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor val Kosmos.tileGridViewModel by diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt index b8d3ff425f20..8ae1332c387a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.ui.viewmodel +package com.android.systemui.qs.panels.ui.viewmodel.toolbar import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel val Kosmos.editModeButtonViewModelFactory by Kosmos.Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt new file mode 100644 index 000000000000..52d8a3aac836 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel.toolbar + +import android.content.applicationContext +import com.android.systemui.classifier.domain.interactor.falsingInteractor +import com.android.systemui.globalactions.globalActionsDialogLite +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.footerActionsInteractor + +val Kosmos.toolbarViewModelFactory by + Kosmos.Fixture { + object : ToolbarViewModel.Factory { + override fun create(): ToolbarViewModel { + return ToolbarViewModel( + editModeButtonViewModelFactory, + footerActionsInteractor, + { globalActionsDialogLite }, + falsingInteractor, + applicationContext, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt index 597d52dcb299..bc1c60c33d71 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.base.interactor +import com.android.systemui.plugins.qs.TileDetailsViewModel +import com.android.systemui.qs.FakeTileDetailsViewModel import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -31,4 +33,7 @@ class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> { override suspend fun handleInput(input: QSTileInput<T>) { mutex.withLock { mutableInputs.add(input) } } + + override var detailsViewModel: TileDetailsViewModel? = + FakeTileDetailsViewModel("FakeQSTileUserActionInteractor") } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt index 6afc0d803f8d..aa6ce9a433df 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toolbar.toolbarViewModelFactory val Kosmos.quickSettingsContainerViewModelFactory by Kosmos.Fixture { @@ -36,6 +37,7 @@ val Kosmos.quickSettingsContainerViewModelFactory by tileGridViewModel, editModeViewModel, detailsViewModel, + toolbarViewModelFactory, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt new file mode 100644 index 000000000000..fa5bd7a90fcf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.statusbar.notificationLockscreenUserManager +import com.android.systemui.statusbar.policy.keyguardStateController +import org.mockito.kotlin.mock + +var Kosmos.dynamicPrivacyController: DynamicPrivacyController by + Kosmos.Fixture { + DynamicPrivacyController( + notificationLockscreenUserManager, + keyguardStateController, + statusBarStateController, + ) + } + +var Kosmos.mockDynamicPrivacyController: DynamicPrivacyController by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt new file mode 100644 index 000000000000..88bf9a5f2d5b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator + +import com.android.keyguard.keyguardUpdateMonitor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.statusbar.notification.dynamicPrivacyController +import com.android.systemui.statusbar.notificationLockscreenUserManager +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController +import com.android.systemui.user.domain.interactor.selectedUserInteractor +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@OptIn(ExperimentalCoroutinesApi::class) +var Kosmos.sensitiveContentCoordinator: SensitiveContentCoordinator by + Kosmos.Fixture { + SensitiveContentCoordinatorImpl( + dynamicPrivacyController, + notificationLockscreenUserManager, + keyguardUpdateMonitor, + statusBarStateController, + keyguardStateController, + selectedUserInteractor, + sensitiveNotificationProtectionController, + deviceEntryInteractor, + sceneInteractor, + testScope.backgroundScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt index 3963d7c5be63..766b280334c8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt @@ -23,5 +23,6 @@ import com.android.systemui.statusbar.StatusBarIconView fun inCallModel( startTimeMs: Long, notificationIcon: StatusBarIconView? = null, - intent: PendingIntent? = null -) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent) + intent: PendingIntent? = null, + notificationKey: String = "test", +) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt new file mode 100644 index 000000000000..8f9184d1dbf3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import com.android.systemui.kosmos.Kosmos +import org.mockito.kotlin.mock + +var Kosmos.sensitiveNotificationProtectionController: SensitiveNotificationProtectionController by + Kosmos.Fixture { mockSensitiveNotificationProtectionController } +val Kosmos.mockSensitiveNotificationProtectionController: + SensitiveNotificationProtectionController by + Kosmos.Fixture { mock<SensitiveNotificationProtectionController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt index c8ba551c518a..34661ced71b2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.haptics.vibratorHelper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor import com.android.systemui.volume.dialog.shared.volumeDialogLogger @@ -31,7 +32,8 @@ val Kosmos.volumeDialogRingerDrawerViewModel by applicationContext = applicationContext, backgroundDispatcher = testDispatcher, coroutineScope = applicationCoroutineScope, - interactor = volumeDialogRingerInteractor, + soundPolicyInteractor = notificationsSoundPolicyInteractor, + ringerInteractor = volumeDialogRingerInteractor, vibrator = vibratorHelper, volumeDialogLogger = volumeDialogLogger, visibilityInteractor = volumeDialogVisibilityInteractor, diff --git a/proto/Android.bp b/proto/Android.bp index a5e13350ebd2..feaa6d2e9b73 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -25,6 +25,10 @@ java_library_static { static_libs: ["libprotobuf-java-nano"], }, }, + apex_available: [ + "com.android.neuralnetworks", + "//apex_available:platform", + ], } java_library_static { diff --git a/services/Android.bp b/services/Android.bp index fc0bb33e6e4e..a7cb9bb9af24 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -210,6 +210,35 @@ crashrecovery_java_defaults { }, } +soong_config_module_type { + name: "ondeviceintelligence_module_java_defaults", + module_type: "java_defaults", + config_namespace: "ANDROID", + bool_variables: [ + "release_ondevice_intelligence_module", + "release_ondevice_intelligence_platform", + ], + properties: [ + "libs", + "srcs", + "static_libs", + ], +} + +// Conditionally add ondeviceintelligence stubs library +ondeviceintelligence_module_java_defaults { + name: "ondeviceintelligence_conditionally", + soong_config_variables: { + release_ondevice_intelligence_module: { + libs: ["service-ondeviceintelligence.stubs.system_server"], + }, + release_ondevice_intelligence_platform: { + srcs: [":service-ondeviceintelligence-sources"], + static_libs: ["modules-utils-backgroundthread"], + }, + }, +} + // merge all required services into one jar // ============================================================ soong_config_module_type { @@ -236,6 +265,7 @@ system_java_library { "services_java_defaults", "art_profile_java_defaults", "services_crashrecovery_stubs_conditionally", + "ondeviceintelligence_conditionally", ], installable: true, diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index f13e22950e2d..c17c34061d1b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -24,12 +24,12 @@ import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_E import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.AppFunctionManager; import android.app.appfunctions.AppFunctionManagerHelper; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; -import android.app.appfunctions.AppFunctionException; import android.app.appfunctions.IAppFunctionEnabledCallback; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; @@ -158,8 +158,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } catch (SecurityException exception) { safeExecuteAppFunctionCallback.onError( new AppFunctionException( - AppFunctionException.ERROR_DENIED, - exception.getMessage())); + AppFunctionException.ERROR_DENIED, exception.getMessage())); return null; } @@ -195,12 +194,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, @NonNull IBinder callerBinder) { UserHandle targetUser = requestInternal.getUserHandle(); - // TODO(b/354956319): Add and honor the new enterprise policies. - if (mCallerValidator.isUserOrganizationManaged(targetUser)) { + UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid); + if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) { safeExecuteAppFunctionCallback.onError( - new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, - "Cannot run on a device with a device owner or from the managed" - + " profile.")); + new AppFunctionException( + AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED, + "Cannot run on a user with a restricted enterprise policy")); return; } @@ -442,7 +441,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (!bindServiceResult) { Slog.e(TAG, "Failed to bind to the AppFunctionService"); safeExecuteAppFunctionCallback.onError( - new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR, + new AppFunctionException( + AppFunctionException.ERROR_SYSTEM_ERROR, "Failed to bind the AppFunctionService.")); } } @@ -495,8 +495,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } FutureGlobalSearchSession futureGlobalSearchSession = - new FutureGlobalSearchSession( - perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR); + new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR); AppFunctionMetadataObserver appFunctionMetadataObserver = new AppFunctionMetadataObserver( user.getUserHandle(), diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java index 5393b939b5ed..61917676e88d 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java @@ -81,10 +81,12 @@ public interface CallerValidator { @NonNull String functionId); /** - * Checks if the user is organization managed. + * Checks if the app function policy is allowed. * + * @param callingUser The current calling user. * @param targetUser The user which the caller is requesting to execute as. - * @return Whether the user is organization managed. + * @return Whether the app function policy is allowed. */ - boolean isUserOrganizationManaged(@NonNull UserHandle targetUser); + boolean verifyEnterprisePolicyIsAllowed( + @NonNull UserHandle callingUser, @NonNull UserHandle targetUser); } diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index e85a70d5845a..69481c32baf0 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -28,6 +28,7 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManager.AppFunctionsPolicy; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; @@ -39,7 +40,6 @@ import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import com.android.internal.infra.AndroidFuture; @@ -124,8 +124,7 @@ class CallerValidatorImpl implements CallerValidator { FutureAppSearchSession futureAppSearchSession = new FutureAppSearchSessionImpl( Objects.requireNonNull( - mContext - .createContextAsUser(targetUser, 0) + mContext.createContextAsUser(targetUser, 0) .getSystemService(AppSearchManager.class)), THREAD_POOL_EXECUTOR, new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); @@ -168,13 +167,16 @@ class CallerValidatorImpl implements CallerValidator { } @Override - public boolean isUserOrganizationManaged(@NonNull UserHandle targetUser) { - if (Objects.requireNonNull(mContext.getSystemService(DevicePolicyManager.class)) - .isDeviceManaged()) { - return true; - } - return Objects.requireNonNull(mContext.getSystemService(UserManager.class)) - .isManagedProfile(targetUser.getIdentifier()); + public boolean verifyEnterprisePolicyIsAllowed( + @NonNull UserHandle callingUser, @NonNull UserHandle targetUser) { + @AppFunctionsPolicy + int callingUserPolicy = getDevicePolicyManagerAsUser(callingUser).getAppFunctionsPolicy(); + @AppFunctionsPolicy + int targetUserPolicy = getDevicePolicyManagerAsUser(targetUser).getAppFunctionsPolicy(); + boolean isSameUser = callingUser.equals(targetUser); + + return isAppFunctionPolicyAllowed(targetUserPolicy, isSameUser) + && isAppFunctionPolicyAllowed(callingUserPolicy, isSameUser); } /** @@ -264,4 +266,18 @@ class CallerValidatorImpl implements CallerValidator { return Process.INVALID_UID; } } + + private boolean isAppFunctionPolicyAllowed( + @AppFunctionsPolicy int userPolicy, boolean isSameUser) { + return switch (userPolicy) { + case DevicePolicyManager.APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY -> true; + case DevicePolicyManager.APP_FUNCTIONS_DISABLED_CROSS_PROFILE -> isSameUser; + default -> false; + }; + } + + private DevicePolicyManager getDevicePolicyManagerAsUser(@NonNull UserHandle targetUser) { + return mContext.createContextAsUser(targetUser, /* flags= */ 0) + .getSystemService(DevicePolicyManager.class); + } } diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index 5e1b1473d233..9c83757f4b0f 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -9,6 +9,16 @@ flag { } flag { + name: "improve_fill_dialog_aconfig" + namespace: "autofill" + description: "Improvements for Fill Dialog. Guard DeviceConfig rollout " + bug: "382493181" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "fill_fields_from_current_session_only" namespace: "autofill" description: "Only fill autofill fields that are part of the current session." diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index 219b788448e8..5e7e557d7041 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -354,6 +354,13 @@ final class AutofillInlineSuggestionsRequestSession { } } + private void handleOnInputMethodStartInputView() { + synchronized (mLock) { + mUiCallback.onInputMethodStartInputView(); + handleOnReceiveImeStatusUpdated(true, true); + } + } + /** * Handles the IME session status received from the IME. * @@ -437,8 +444,8 @@ final class AutofillInlineSuggestionsRequestSession { final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { session.mHandler.sendMessage(obtainMessage( - AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, - session, true, true)); + AutofillInlineSuggestionsRequestSession::handleOnInputMethodStartInputView, + session)); } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 259ea148f163..cba8c66cd5e3 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -2139,6 +2139,32 @@ public final class AutofillManagerService } @Override + public void notifyImeAnimationStart(int sessionId, long startTimeMs, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.notifyImeAnimationStart(sessionId, startTimeMs, getCallingUid()); + } else if (sVerbose) { + Slog.v(TAG, "notifyImeAnimationStart(): no service for " + userId); + } + } + } + + @Override + public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.notifyImeAnimationEnd(sessionId, endTimeMs, getCallingUid()); + } else if (sVerbose) { + Slog.v(TAG, "notifyImeAnimationEnd(): no service for " + userId); + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 5cf96bfb2b8b..0fa43ac7091b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -823,6 +823,36 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") + public void notifyImeAnimationStart(int sessionId, long startTimeMs, int uid) { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.v(TAG, "notifyImeAnimationStart(): no session for " + + sessionId + "(" + uid + ")"); + return; + } + session.notifyImeAnimationStart(startTimeMs); + } + + @GuardedBy("mLock") + public void notifyImeAnimationEnd(int sessionId, long endTimeMs, int uid) { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.v(TAG, "notifyImeAnimationEnd(): no session for " + + sessionId + "(" + uid + ")"); + return; + } + session.notifyImeAnimationEnd(endTimeMs); + } + + @GuardedBy("mLock") @Override // from PerUserSystemService protected void handlePackageUpdateLocked(@NonNull String packageName) { final ServiceInfo serviceInfo = mFieldClassificationStrategy.getServiceInfo(); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6b227d7a876e..9c6e4741730a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -40,6 +40,7 @@ import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; import static android.service.autofill.Flags.highlightAutofillSingleField; +import static android.service.autofill.Flags.improveFillDialogAconfig; import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; @@ -50,6 +51,7 @@ import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; + import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; @@ -184,6 +186,7 @@ import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; import android.view.inputmethod.InlineSuggestionsRequest; import android.widget.RemoteViews; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -195,6 +198,7 @@ import com.android.server.autofill.ui.InlineFillUi; import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -248,6 +252,8 @@ final class Session private static final int DEFAULT__FILL_REQUEST_ID_SNAPSHOT = -2; private static final int DEFAULT__FIELD_CLASSIFICATION_REQUEST_ID_SNAPSHOT = -2; + private static final long DEFAULT_UNASSIGNED_TIME = -1; + static final String SESSION_ID_KEY = "autofill_session_id"; static final String REQUEST_ID_KEY = "autofill_request_id"; @@ -292,6 +298,31 @@ final class Session @Retention(RetentionPolicy.SOURCE) @interface SessionState {} + /** + * Indicates fill dialog will not be shown. + */ + private static final int SHOW_FILL_DIALOG_NO = 0; + + /** + * Indicates fill dialog is shown. + */ + private static final int SHOW_FILL_DIALOG_YES = 1; + + /** + * Indicates fill dialog can be shown, but we need to wait. + * For eg, if the IME animation is happening, we need for it to complete before fill dialog can + * be shown. + */ + private static final int SHOW_FILL_DIALOG_WAIT = 2; + + @IntDef(prefix = { "SHOW_FILL_DIALOG_" }, value = { + SHOW_FILL_DIALOG_NO, + SHOW_FILL_DIALOG_YES, + SHOW_FILL_DIALOG_WAIT + }) + @Retention(RetentionPolicy.SOURCE) + private @interface ShowFillDialogState{} + @GuardedBy("mLock") private final SessionFlags mSessionFlags; @@ -576,6 +607,47 @@ final class Session private boolean mIgnoreViewStateResetToEmpty; + /** + * Whether improveFillDialog feature is enabled or not. + * Configured via device config flag and aconfig flag. + */ + private final boolean mImproveFillDialogEnabled; + + /** + * Timeout, after which, fill dialog won't be shown. + * Configured via device config flag. + */ + private final long mFillDialogTimeoutMs; + + /** + * Time to wait after ime Animation ends before showing up fill dialog. + * Configured via device config flag. + */ + private final long mFillDialogMinWaitAfterImeAnimationMs; + + /** + * Indicates the need to wait for ime animation to end before showing up fill dialog. + * This is set when we receive notification of ime animation start. + * Focussing on one input field from another wouldn't cause ime to re-animate. So this will let + * us know whether we need to wait for ime animation finish notification. + */ + private boolean mWaitForImeAnimation; + + /** + * A runnable set to run when there is a need to wait for ime animation to end before showing + * up fill dialog. This is set only if the fill response has been received, and the response + * is eligible for showing up fill dialog, but the ime animation hasn't completed. This allows + * for this runnable to be scheduled/run when the ime animation ends, in order to show fill + * dialog. + */ + private Runnable mFillDialogRunnable; + + private long mImeAnimationFinishTimeMs = DEFAULT_UNASSIGNED_TIME; + + private long mImeAnimationStartTimeMs = DEFAULT_UNASSIGNED_TIME; + + private long mLastInputStartTime = DEFAULT_UNASSIGNED_TIME; + /* * Id of the previous view that was entered. Once set, it would only be replaced by non-null * view ids. @@ -1493,6 +1565,12 @@ final class Session // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); + if (mImproveFillDialogEnabled) { + // New request has been sent, so re-enable fill dialog. + // Fill dialog is eligible to be shown after each Fill request. + enableFillDialog(); + } + return Optional.of(requestId); } @@ -1657,6 +1735,11 @@ final class Session mSaveEventLogger = SaveEventLogger.forSessionId(sessionId, mLatencyBaseTime); mIsPrimaryCredential = isPrimaryCredential; mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty(); + mImproveFillDialogEnabled = + improveFillDialogAconfig() && AutofillFeatureFlags.isImproveFillDialogEnabled(); + mFillDialogTimeoutMs = AutofillFeatureFlags.getFillDialogTimeoutMs(); + mFillDialogMinWaitAfterImeAnimationMs = + AutofillFeatureFlags.getFillDialogMinWaitAfterImeAnimationtEndMs(); synchronized (mLock) { mSessionFlags = new SessionFlags(); @@ -1682,6 +1765,13 @@ final class Session public void notifyInlineUiHidden(AutofillId autofillId) { notifyFillUiHidden(autofillId); } + + @Override + public void onInputMethodStartInputView() { + // TODO(b/377868687): This method isn't called when IME doesn't + // support inline suggestion. Handle those cases as well. + onInputMethodStart(); + } }); mMetricsLogger.write( @@ -3044,6 +3134,12 @@ final class Session } } + private void onInputMethodStart() { + synchronized (mLock) { + mLastInputStartTime = SystemClock.elapsedRealtime(); + } + } + private void doStartIntentSender(IntentSender intentSender, Intent intent) { try { synchronized (mLock) { @@ -5407,6 +5503,15 @@ final class Session } } + private void resetImeAnimationState() { + synchronized (mLock) { + mWaitForImeAnimation = false; + mImeAnimationStartTimeMs = DEFAULT_UNASSIGNED_TIME; + mImeAnimationFinishTimeMs = DEFAULT_UNASSIGNED_TIME; + mLastInputStartTime = DEFAULT_UNASSIGNED_TIME; + } + } + @Override public void onFillReady( @NonNull FillResponse response, @@ -5452,18 +5557,24 @@ final class Session final AutofillId[] ids = response.getFillDialogTriggerIds(); if (ids != null && ArrayUtils.contains(ids, filledId)) { - if (requestShowFillDialog(response, filledId, filterText, flags)) { + @ShowFillDialogState int fillDialogState = + requestShowFillDialog(response, filledId, filterText, flags); + if (fillDialogState == SHOW_FILL_DIALOG_YES) { synchronized (mLock) { final ViewState currentView = mViewStates.get(mCurrentViewId); currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); } - // Just show fill dialog once, so disabled after shown. + // Just show fill dialog once per fill request, so disabled after shown. // Note: Cannot disable before requestShowFillDialog() because the method - // need to check whether fill dialog enabled. + // need to check whether fill dialog is enabled. setFillDialogDisabled(); + resetImeAnimationState(); return; - } else { + } else if (fillDialogState == SHOW_FILL_DIALOG_NO) { + resetImeAnimationState(); setFillDialogDisabled(); + } else { // SHOW_FILL_DIALOG_WAIT + return; } } @@ -5559,7 +5670,20 @@ final class Session } } + private void enableFillDialog() { + if (sVerbose) { + Slog.v(TAG, "Enabling Fill Dialog...."); + } + synchronized (mLock) { + mSessionFlags.mFillDialogDisabled = false; + } + notifyClientFillDialogTriggerIds(null); + } + private void setFillDialogDisabled() { + if (sVerbose) { + Slog.v(TAG, "Disabling Fill Dialog."); + } synchronized (mLock) { mSessionFlags.mFillDialogDisabled = true; } @@ -5577,24 +5701,28 @@ final class Session } } - private boolean requestShowFillDialog( + private @ShowFillDialogState int requestShowFillDialog( FillResponse response, AutofillId filledId, String filterText, int flags) { if (!isFillDialogUiEnabled()) { + // TODO(b/377868687): The above check includes credman fields. We may want to show + // credman fields again. // Unsupported fill dialog UI - if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled"); - return false; + if (sDebug) Log.w(TAG, "requestShowFillDialog(): fill dialog is disabled"); + return SHOW_FILL_DIALOG_NO; } - if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) { - // IME is showing, fallback to normal suggestions UI - if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing"); - return false; - } + if (!mImproveFillDialogEnabled) { + if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) { + // IME is showing, fallback to normal suggestions UI + if (sDebug) Log.w(TAG, "requestShowFillDialog(): IME is showing"); + return SHOW_FILL_DIALOG_NO; + } - if (mInlineSessionController.isImeShowing()) { - // IME is showing, fallback to normal suggestions UI - // Note: only work when inline suggestions supported - return false; + if (mInlineSessionController.isImeShowing()) { + // IME is showing, fallback to normal suggestions UI + // Note: only work when inline suggestions supported + return SHOW_FILL_DIALOG_NO; + } } synchronized (mLock) { @@ -5602,29 +5730,84 @@ final class Session || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) { // Last fill dialog triggered ids are changed. if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); - return false; + return SHOW_FILL_DIALOG_NO; + } + + if (mImproveFillDialogEnabled && mInlineSessionController.isImeShowing()) { + long currentTimestampMs = SystemClock.elapsedRealtime(); + long durationMs = currentTimestampMs - mLastInputStartTime; + if (sVerbose) { + Log.d(TAG, "IME is showing. Checking for elapsed time "); + Log.d(TAG, "IME is showing. Timestamps start: " + mLastInputStartTime + + " current: " + currentTimestampMs + " duration: " + durationMs + + " mFillDialogTimeoutMs: " + mFillDialogTimeoutMs); + } + + // Following situations can arise wrt IME animation. + // 1. No animation happening (eg IME already animated). In that case, + // mWaitForImeAnimation should be false. This is possible if the IME is already up + // on a field, but the user focusses on another field. Under such condition, + // since IME has already animated, there won't be another animation. However, + // onInputStartInputView is still called. + // 2. Animation is still proceeding. We should wait for animation to finish, + // and then proceed. + // 3. Animation is complete. + if (mWaitForImeAnimation) { + // we need to wait for animation to happen. We can't return from here yet. + // This is the situation #2 described above. + Log.d(TAG, "Waiting for ime animation to complete before showing fill dialog"); + mFillDialogRunnable = createFillDialogEvalRunnable( + response, filledId, filterText, flags); + return SHOW_FILL_DIALOG_WAIT; + } + + // Incorporate situations 1 & 3 discussed above. We calculate the duration from the + // max of start input time or the ime finish time + long effectiveDuration = currentTimestampMs + - Math.max(mLastInputStartTime, mImeAnimationFinishTimeMs); + if (effectiveDuration >= mFillDialogTimeoutMs) { + Log.d(TAG, "Fill dialog not shown since IME has been up for more time than " + + mFillDialogTimeoutMs + "ms"); + return SHOW_FILL_DIALOG_NO; + } else if (effectiveDuration < mFillDialogMinWaitAfterImeAnimationMs) { + // we need to wait for some time after animation ends + Runnable runnable = createFillDialogEvalRunnable( + response, filledId, filterText, flags); + mHandler.postDelayed(runnable, + mFillDialogMinWaitAfterImeAnimationMs - effectiveDuration); + return SHOW_FILL_DIALOG_WAIT; + } } } + showFillDialog(response, filledId, filterText); + return SHOW_FILL_DIALOG_YES; + } + + private Runnable createFillDialogEvalRunnable( + @NonNull FillResponse response, + @NonNull AutofillId filledId, + String filterText, + int flags) { + return () -> { + synchronized (mLock) { + AutofillValue value = AutofillValue.forText(filterText); + onFillReady(response, filledId, value, flags); + } + }; + } + + private void showFillDialog(FillResponse response, AutofillId filledId, String filterText) { Drawable serviceIcon = null; + PresentationStatsEventLogger logger = null; synchronized (mLock) { serviceIcon = getServiceIcon(response); + logger = mPresentationStatsEventLogger; } - getUiForShowing() - .showFillDialog( - filledId, - response, - filterText, - mService.getServicePackageName(), - mComponentName, - serviceIcon, - this, - id, - mCompatMode, - mPresentationStatsEventLogger, - mLock); - return true; + getUiForShowing().showFillDialog(filledId, response, filterText, + mService.getServicePackageName(), mComponentName, serviceIcon, this, + id, mCompatMode, logger, mLock); } /** @@ -7689,6 +7872,30 @@ final class Session mSessionState = STATE_REMOVED; } + @GuardedBy("mLock") + public void notifyImeAnimationStart(long startTimeMs) { + mImeAnimationStartTimeMs = startTimeMs; + mWaitForImeAnimation = true; + } + + @GuardedBy("mLock") + public void notifyImeAnimationEnd(long endTimeMs) { + mImeAnimationFinishTimeMs = endTimeMs; + // Make sure to use mRunnable with synchronized + if (mFillDialogRunnable != null) { + if (sVerbose) { + Log.v(TAG, "Ime animation ended, starting fill dialog."); + } + mHandler.postDelayed(mFillDialogRunnable, mFillDialogMinWaitAfterImeAnimationMs); + mFillDialogRunnable = null; + } else { + if (sVerbose) { + Log.v(TAG, "Ime animation ended, no runnable present."); + } + } + mWaitForImeAnimation = false; + } + void onPendingSaveUi(int operation, @NonNull IBinder token) { getUiForShowing().onPendingSaveUi(operation, token); } diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java index ffc80ee7d710..7287bdd8e34f 100644 --- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java @@ -409,5 +409,10 @@ public final class InlineFillUi { * Callback to notify inline ui is hidden. */ void notifyInlineUiHidden(@NonNull AutofillId autofillId); + + /** + * Callback to notify input method started. + */ + void onInputMethodStartInputView(); } } diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java index 4bcfb33b9e7c..7c8604f06b10 100644 --- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java +++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java @@ -240,9 +240,9 @@ class BackupRestoreProcessor { boolean matchesMacAddress = Objects.equals( associationInfo.getDeviceMacAddress(), restored.getDeviceMacAddress()); - boolean matchesDeviceId = !Flags.associationTag() - || (associationInfo.getDeviceId() != null - && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId())); + boolean matchesDeviceId = Flags.associationTag() + && (associationInfo.getDeviceId() != null + && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId())); return matchesMacAddress || matchesDeviceId; }; AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice); diff --git a/services/core/Android.bp b/services/core/Android.bp index ffa259b536ec..b3d85f8aac48 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -127,6 +127,7 @@ java_library_static { "android.hardware.power-java_shared", "latest_android_hardware_broadcastradio_java_static", "services_crashrecovery_stubs_conditionally", + "ondeviceintelligence_conditionally", ], srcs: [ ":android.hardware.tv.hdmi.connection-V1-java-source", @@ -157,8 +158,8 @@ java_library_static { "android.hardware.light-V2.0-java", "android.hardware.gnss-V2-java", "android.hardware.vibrator-V3-java", + "androidx.annotation_annotation", "app-compat-annotations", - "art_exported_aconfig_flags_lib", "framework-tethering.stubs.module_lib", "keepanno-annotations", "service-art.stubs.system_server", @@ -245,6 +246,7 @@ java_library_static { "locksettings_flags_lib", "profiling_flags_lib", "android.adpf.sessionmanager_aidl-java", + "uprobestats_flags_java_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index eba9a25717dd..bd7a0ac55117 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -449,6 +449,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private AtomicBoolean mIsSatelliteEnabled; private AtomicBoolean mWasSatelliteEnabledNotified; + private final int mPid = Process.myPid(); + /** * Per-phone map of precise data connection state. The key of the map is the pair of transport * type and APN setting. This is the cache to prevent redundant callbacks to the listeners. @@ -1441,7 +1443,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) { try { - r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId)); + if (Flags.passCopiedCallStateList()) { + List<CallState> callList; + if (r.callerPid == mPid) { + callList = List.copyOf(mCallStateLists.get(r.phoneId)); + } else { + callList = mCallStateLists.get(r.phoneId); + } + r.callback.onCallStatesChanged(callList); + } else { + r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId)); + } } catch (RemoteException ex) { remove(r.binder); } @@ -2573,12 +2585,25 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (notifyCallState) { + List<CallState> copyList = null; for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r, subId, phoneId)) { + // If listener is in the same process, original instance can be passed + // to the listener via AIDL without serialization/de-serialization. We + // will pass the copied list. Since the element is newly created instead + // of modification for the change, we can use shallow copy for this. try { - r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); + if (Flags.passCopiedCallStateList()) { + if (r.callerPid == mPid && copyList == null) { + copyList = List.copyOf(mCallStateLists.get(phoneId)); + } + r.callback.onCallStatesChanged(copyList == null + ? mCallStateLists.get(phoneId) : copyList); + } else { + r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); + } } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -2906,13 +2931,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { log("There is no active call to report CallQuality"); return; } - + List<CallState> copyList = null; for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r, subId, phoneId)) { try { - r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); + if (Flags.passCopiedCallStateList()) { + if (r.callerPid == mPid && copyList == null) { + copyList = List.copyOf(mCallStateLists.get(phoneId)); + } + r.callback.onCallStatesChanged(copyList == null + ? mCallStateLists.get(phoneId) : copyList); + } else { + r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); + } } catch (RemoteException ex) { mRemoveList.add(r.binder); } diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 3ce645158fe4..719928b8e582 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -2846,6 +2846,14 @@ public class AccountManagerService : AccountsDb.DEBUG_ACTION_SET_PASSWORD; logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts, callingUid); + + FrameworkStatsLog.write( + FrameworkStatsLog.ACCOUNT_MANAGER_EVENT, + account.type, + callingUid, + TextUtils.isEmpty(password) + ? FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_REMOVED + : FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_CHANGED); } } finally { accounts.accountsDb.endTransaction(); @@ -2912,7 +2920,7 @@ public class AccountManagerService if (!accountExistsCache(accounts, account)) { return; } - setUserdataInternal(accounts, account, key, value); + setUserdataInternal(accounts, account, key, value, callingUid); } finally { restoreCallingIdentity(identityToken); } @@ -2932,7 +2940,7 @@ public class AccountManagerService } private void setUserdataInternal(UserAccounts accounts, Account account, String key, - String value) { + String value, int callingUid) { synchronized (accounts.dbLock) { accounts.accountsDb.beginTransaction(); try { @@ -2958,6 +2966,11 @@ public class AccountManagerService AccountManager.invalidateLocalAccountUserDataCaches(); } } + FrameworkStatsLog.write( + FrameworkStatsLog.ACCOUNT_MANAGER_EVENT, + account.type, + callingUid, + FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__USER_DATA_CHANGED); } private void onResult(IAccountManagerResponse response, Bundle result) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ebe7fa5e5a3f..cd929c1883d0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -361,6 +361,8 @@ import android.os.WorkSource; import android.os.incremental.IIncrementalService; import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalMetrics; +import android.os.instrumentation.IOffsetCallback; +import android.os.instrumentation.MethodDescriptor; import android.os.storage.IStorageManager; import android.os.storage.StorageManager; import android.provider.DeviceConfig; @@ -509,6 +511,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -2781,9 +2784,7 @@ public class ActivityManagerService extends IActivityManager.Stub mIsolatedAppBindArgs = new ArrayMap<>(1); // See b/79378449 about the following exemption. addServiceToMap(mIsolatedAppBindArgs, "package"); - if (!android.server.Flags.removeJavaServiceManagerCache()) { - addServiceToMap(mIsolatedAppBindArgs, "permissionmgr"); - } + addServiceToMap(mIsolatedAppBindArgs, "permissionmgr"); } return mIsolatedAppBindArgs; } @@ -2794,38 +2795,27 @@ public class ActivityManagerService extends IActivityManager.Stub // Add common services. // IMPORTANT: Before adding services here, make sure ephemeral apps can access them too. // Enable the check in ApplicationThread.bindApplication() to make sure. - - // Removing User Service and App Ops Service from cache breaks boot for auto. - // Removing permissionmgr breaks tests for Android Auto due to SELinux restrictions. - // TODO: fix SELinux restrictions and remove caching for Android Auto. - if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) - || !android.server.Flags.removeJavaServiceManagerCache()) { - addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE); - addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE); - addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE); - addServiceToMap(mAppBindArgs, Context.CONNECTIVITY_SERVICE); - addServiceToMap(mAppBindArgs, Context.ACCESSIBILITY_SERVICE); - addServiceToMap(mAppBindArgs, Context.INPUT_METHOD_SERVICE); - addServiceToMap(mAppBindArgs, Context.INPUT_SERVICE); - addServiceToMap(mAppBindArgs, "graphicsstats"); - addServiceToMap(mAppBindArgs, "content"); - addServiceToMap(mAppBindArgs, Context.JOB_SCHEDULER_SERVICE); - addServiceToMap(mAppBindArgs, Context.NOTIFICATION_SERVICE); - addServiceToMap(mAppBindArgs, Context.VIBRATOR_SERVICE); - addServiceToMap(mAppBindArgs, Context.ACCOUNT_SERVICE); - addServiceToMap(mAppBindArgs, Context.POWER_SERVICE); - addServiceToMap(mAppBindArgs, "mount"); - addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE); - } - // See b/79378449 - // Getting the window service and package service binder from servicemanager - // is blocked for Apps. However they are necessary for apps. - // TODO: remove exception addServiceToMap(mAppBindArgs, "package"); - addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE); - addServiceToMap(mAppBindArgs, Context.USER_SERVICE); addServiceToMap(mAppBindArgs, "permissionmgr"); + addServiceToMap(mAppBindArgs, Context.WINDOW_SERVICE); + addServiceToMap(mAppBindArgs, Context.ALARM_SERVICE); + addServiceToMap(mAppBindArgs, Context.DISPLAY_SERVICE); + addServiceToMap(mAppBindArgs, Context.NETWORKMANAGEMENT_SERVICE); + addServiceToMap(mAppBindArgs, Context.CONNECTIVITY_SERVICE); + addServiceToMap(mAppBindArgs, Context.ACCESSIBILITY_SERVICE); + addServiceToMap(mAppBindArgs, Context.INPUT_METHOD_SERVICE); + addServiceToMap(mAppBindArgs, Context.INPUT_SERVICE); + addServiceToMap(mAppBindArgs, "graphicsstats"); addServiceToMap(mAppBindArgs, Context.APP_OPS_SERVICE); + addServiceToMap(mAppBindArgs, "content"); + addServiceToMap(mAppBindArgs, Context.JOB_SCHEDULER_SERVICE); + addServiceToMap(mAppBindArgs, Context.NOTIFICATION_SERVICE); + addServiceToMap(mAppBindArgs, Context.VIBRATOR_SERVICE); + addServiceToMap(mAppBindArgs, Context.ACCOUNT_SERVICE); + addServiceToMap(mAppBindArgs, Context.POWER_SERVICE); + addServiceToMap(mAppBindArgs, Context.USER_SERVICE); + addServiceToMap(mAppBindArgs, "mount"); + addServiceToMap(mAppBindArgs, Context.PLATFORM_COMPAT_SERVICE); } return mAppBindArgs; } @@ -18055,6 +18045,26 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public void getExecutableMethodFileOffsets(@NonNull String processName, + int pid, int uid, @NonNull MethodDescriptor methodDescriptor, + @NonNull IOffsetCallback callback) { + final IApplicationThread thread; + synchronized (ActivityManagerService.this) { + ProcessRecord record = mProcessList.getProcessRecordLocked(processName, uid); + if (record == null || record.getPid() != pid) { + throw new NoSuchElementException(); + } + thread = record.getThread(); + } + try { + thread.getExecutableMethodFileOffsets(methodDescriptor, callback); + } catch (RemoteException e) { + throw new RuntimeException( + "IApplicationThread.getExecutableMethodFileOffsets failed", e); + } + } + + @Override public void addCreatorToken(Intent intent, String creatorPackage) { ActivityManagerService.this.addCreatorToken(intent, creatorPackage); } @@ -19406,7 +19416,7 @@ public class ActivityManagerService extends IActivityManager.Stub return createOrGetIntentCreatorToken(intent, key); } else { - throw new IllegalArgumentException("intent does not contain a creator token."); + return null; } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index aea24d978bee..c27126a01a32 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1101,9 +1101,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub /** StatsPullAtomCallback for pulling BatteryUsageStats data. */ private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback { - private static final long BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE = - TimeUnit.HOURS.toMillis(2); - @Override public int onPullAtom(int atomTag, List<StatsEvent> data) { final BatteryUsageStats bus; @@ -1171,8 +1168,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub .setMinConsumedPowerThreshold(minConsumedPowerThreshold); if (isBatteryUsageStatsAccumulationSupported()) { - query.accumulated() - .setMaxStatsAgeMs(BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE); + query.accumulated(); } bus = getBatteryUsageStats(List.of(query.build())).get(0); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 8bb80fdff9c4..883e09f53e41 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -42,6 +42,7 @@ import android.aconfigd.Aconfigd.StorageReturnMessages; import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides; import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately; +import static com.android.aconfig_new_storage.Flags.enableAconfigdFromMainline; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -215,6 +216,7 @@ public class SettingsToPropertiesMapper { "pixel_bluetooth", "pixel_connectivity_gps", "pixel_continuity", + "pixel_display", "pixel_perf", "pixel_sensors", "pixel_state_server", @@ -464,16 +466,38 @@ public class SettingsToPropertiesMapper { * @param requests: request proto output stream * @return aconfigd socket return as proto input stream */ - static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) { + static void sendAconfigdRequests(ProtoOutputStream requests) { + ProtoInputStream returns = sendAconfigdRequests("aconfigd_system", requests); + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); + } + if (enableAconfigdFromMainline()) { + returns = sendAconfigdRequests("aconfigd_mainline", requests); + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); + } + } + } + + /** + * apply flag local override in aconfig new storage + * @param socketName: the socket to send to + * @param requests: request proto output stream + * @return aconfigd socket return as proto input stream + */ + static ProtoInputStream sendAconfigdRequests(String socketName, ProtoOutputStream requests) { // connect to aconfigd socket LocalSocket client = new LocalSocket(); - String socketName = "aconfigd_system"; try { client.connect(new LocalSocketAddress( socketName, LocalSocketAddress.Namespace.RESERVED)); - Slog.d(TAG, "connected to aconfigd socket"); + Slog.d(TAG, "connected to " + socketName + " socket"); } catch (IOException ioe) { - logErr("failed to connect to aconfigd socket", ioe); + logErr("failed to connect to " + socketName + " socket", ioe); return null; } @@ -492,9 +516,9 @@ public class SettingsToPropertiesMapper { byte[] requests_bytes = requests.getBytes(); outputStream.writeInt(requests_bytes.length); outputStream.write(requests_bytes, 0, requests_bytes.length); - Slog.d(TAG, "flag override requests sent to aconfigd"); + Slog.d(TAG, "flag override requests sent to " + socketName); } catch (IOException ioe) { - logErr("failed to send requests to aconfigd", ioe); + logErr("failed to send requests to " + socketName, ioe); return null; } @@ -502,10 +526,10 @@ public class SettingsToPropertiesMapper { try { int num_bytes = inputStream.readInt(); ProtoInputStream returns = new ProtoInputStream(inputStream); - Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd"); + Slog.d(TAG, "received " + num_bytes + " bytes back from " + socketName); return returns; } catch (IOException ioe) { - logErr("failed to read requests return from aconfigd", ioe); + logErr("failed to read requests return from " + socketName, ioe); return null; } } @@ -646,15 +670,8 @@ public class SettingsToPropertiesMapper { return; } - // send requests to aconfigd and obtain the return byte buffer - ProtoInputStream returns = sendAconfigdRequests(requests); - - // deserialize back using proto input stream - try { - parseAndLogAconfigdReturn(returns); - } catch (IOException ioe) { - logErr("failed to parse aconfigd return", ioe); - } + // send requests to aconfigd + sendAconfigdRequests(requests); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -764,15 +781,8 @@ public class SettingsToPropertiesMapper { return; } - // send requests to aconfigd and obtain the return - ProtoInputStream returns = sendAconfigdRequests(requests); - - // deserialize back using proto input stream - try { - parseAndLogAconfigdReturn(returns); - } catch (IOException ioe) { - logErr("failed to parse aconfigd return", ioe); - } + // send requests to aconfigd + sendAconfigdRequests(requests); } /** diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 295e0443371d..06c586f5e9c2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -3191,7 +3191,7 @@ public class AppOpsService extends IAppOpsService.Stub { resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted, - "proxy " + message, shouldCollectMessage, 1); + "proxy " + message, shouldCollectMessage); if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, proxiedPackageName); @@ -3210,20 +3210,7 @@ public class AppOpsService extends IAppOpsService.Stub { return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName, proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, 1); - } - - @Override - public void noteOperationsInBatch(Map batchedNoteOps) { - for (var entry : ((Map<AppOpsManager.NotedOp, Integer>) batchedNoteOps).entrySet()) { - AppOpsManager.NotedOp notedOp = entry.getKey(); - int notedCount = entry.getValue(); - mCheckOpsDelegateDispatcher.noteOperation( - notedOp.getOp(), notedOp.getUid(), notedOp.getPackageName(), - notedOp.getAttributionTag(), notedOp.getVirtualDeviceId(), - notedOp.getShouldCollectAsyncNotedOp(), notedOp.getMessage(), - notedOp.getShouldCollectMessage(), notedCount); - } + message, shouldCollectMessage); } @Override @@ -3241,7 +3228,7 @@ public class AppOpsService extends IAppOpsService.Stub { } return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, 1); + shouldCollectMessage); } @Override @@ -3250,12 +3237,13 @@ public class AppOpsService extends IAppOpsService.Stub { String message, boolean shouldCollectMessage) { return mCheckOpsDelegateDispatcher.noteOperation(code, uid, packageName, attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, 1); + shouldCollectMessage); } private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName, - @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, int notedCount) { + @Nullable String attributionTag, int virtualDeviceId, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage) { String resolvedPackageName; if (!shouldUseNewCheckOp()) { verifyIncomingUid(uid); @@ -3290,14 +3278,14 @@ public class AppOpsService extends IAppOpsService.Stub { return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, notedCount); + message, shouldCollectMessage); } private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId, @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage, int notedCount) { + boolean shouldCollectMessage) { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3400,11 +3388,11 @@ public class AppOpsService extends IAppOpsService.Stub { virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED); attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags, notedCount); + getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, - shouldCollectMessage, notedCount); + shouldCollectMessage); } return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, @@ -3563,7 +3551,7 @@ public class AppOpsService extends IAppOpsService.Stub { */ private void collectAsyncNotedOp(int uid, @NonNull String packageName, int opCode, @Nullable String attributionTag, @OpFlags int flags, @NonNull String message, - boolean shouldCollectMessage, int notedCount) { + boolean shouldCollectMessage) { Objects.requireNonNull(message); int callingUid = Binder.getCallingUid(); @@ -3571,51 +3559,42 @@ public class AppOpsService extends IAppOpsService.Stub { final long token = Binder.clearCallingIdentity(); try { synchronized (this) { + Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); + + RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); + AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid, + attributionTag, message, System.currentTimeMillis()); + final boolean[] wasNoteForwarded = {false}; + if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) != 0 && shouldCollectMessage) { reportRuntimeAppOpAccessMessageAsyncLocked(uid, packageName, opCode, attributionTag, message); } - Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); - RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); - if (callbacks == null) { - return; - } - - final boolean[] wasNoteForwarded = {false}; - if (Flags.rateLimitBatchedNoteOpAsyncCallbacksEnabled()) { - notedCount = 1; - } - - for (int i = 0; i < notedCount; i++) { - AsyncNotedAppOp asyncNotedOp = new AsyncNotedAppOp(opCode, callingUid, - attributionTag, message, System.currentTimeMillis()); - wasNoteForwarded[0] = false; + if (callbacks != null) { callbacks.broadcast((cb) -> { try { cb.opNoted(asyncNotedOp); wasNoteForwarded[0] = true; } catch (RemoteException e) { Slog.e(TAG, - "Could not forward noteOp of " + opCode + " to " - + packageName + "Could not forward noteOp of " + opCode + " to " + packageName + "/" + uid + "(" + attributionTag + ")", e); } }); + } - if (!wasNoteForwarded[0]) { - ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get( - key); - if (unforwardedOps == null) { - unforwardedOps = new ArrayList<>(1); - mUnforwardedAsyncNotedOps.put(key, unforwardedOps); - } + if (!wasNoteForwarded[0]) { + ArrayList<AsyncNotedAppOp> unforwardedOps = mUnforwardedAsyncNotedOps.get(key); + if (unforwardedOps == null) { + unforwardedOps = new ArrayList<>(1); + mUnforwardedAsyncNotedOps.put(key, unforwardedOps); + } - unforwardedOps.add(asyncNotedOp); - if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) { - unforwardedOps.remove(0); - } + unforwardedOps.add(asyncNotedOp); + if (unforwardedOps.size() > MAX_UNFORWARDED_OPS) { + unforwardedOps.remove(0); } } } @@ -4047,7 +4026,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (shouldCollectAsyncNotedOp && !isRestricted) { collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF, - message, shouldCollectMessage, 1); + message, shouldCollectMessage); } return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag, @@ -7595,36 +7574,34 @@ public class AppOpsService extends IAppOpsService.Stub { public SyncNotedAppOp noteOperation(int code, int uid, String packageName, String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, - String message, boolean shouldCollectMessage, int notedCount) { + String message, boolean shouldCollectMessage) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { return mPolicy.noteOperation(code, uid, packageName, attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, notedCount, this::noteDelegateOperationImpl + shouldCollectMessage, this::noteDelegateOperationImpl ); } else { return mPolicy.noteOperation(code, uid, packageName, attributionTag, virtualDeviceId, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, notedCount, AppOpsService.this::noteOperationImpl + shouldCollectMessage, AppOpsService.this::noteOperationImpl ); } } else if (mCheckOpsDelegate != null) { return noteDelegateOperationImpl(code, uid, packageName, attributionTag, - virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - notedCount); + virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } return noteOperationImpl(code, uid, packageName, attributionTag, - virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - notedCount); + virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage); } private SyncNotedAppOp noteDelegateOperationImpl(int code, int uid, @Nullable String packageName, @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage, int notedCount) { + boolean shouldCollectMessage) { return mCheckOpsDelegate.noteOperation(code, uid, packageName, featureId, virtualDeviceId, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - notedCount, AppOpsService.this::noteOperationImpl + AppOpsService.this::noteOperationImpl ); } diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 4d114b4ad4ac..314664b0a79d 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -100,12 +100,10 @@ final class AttributedOp { * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call - * @param accessCount The number of times the op is accessed */ public void accessed(int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, - @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, - int accessCount) { + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { long accessTime = System.currentTimeMillis(); accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags); @@ -113,7 +111,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE, - DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount); + DiscreteRegistry.ACCESS_TYPE_NOTE_OP); } /** @@ -257,7 +255,7 @@ final class AttributedOp { if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, startTime, - attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1); + attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP); } } @@ -453,7 +451,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), - event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1); + event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), true, diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 5e67f26ba1f6..6b0253864e2b 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -475,7 +475,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteRegistry.AccessType int accessType, int accessCount) { + @DiscreteRegistry.AccessType int accessType) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -484,7 +484,7 @@ final class HistoricalRegistry { } getUpdatedPendingHistoricalOpsMLocked( System.currentTimeMillis()).increaseAccessCount(op, uid, packageName, - attributionTag, uidState, flags, accessCount); + attributionTag, uidState, flags, 1); mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, uidState, accessTime, -1, attributionFlags, diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 5c6299559856..452dc5f97d12 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -47,6 +47,7 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; +import static android.provider.Settings.Secure.MIRROR_BUILT_IN_DISPLAY; import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL; import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH; import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN; @@ -71,6 +72,7 @@ import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -534,6 +536,8 @@ public final class DisplayManagerService extends SystemService { private final boolean mExtraDisplayEventLogging; private final String mExtraDisplayLoggingPackageName; + private boolean mMirrorBuiltInDisplay; + private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -782,7 +786,11 @@ public final class DisplayManagerService extends SystemService { } dpc.onSwitchUser(newUserId, userSerial, newBrightness); }); - handleSettingsChange(); + handleMinimalPostProcessingAllowedSettingChange(); + + if (mFlags.isDisplayContentModeManagementEnabled()) { + updateMirrorBuiltInDisplaySettingLocked(); + } } } @@ -825,12 +833,15 @@ public final class DisplayManagerService extends SystemService { // relevant configuration should be in place. recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); - updateSettingsLocked(); + updateMinimalPostProcessingAllowedSettingLocked(); updateUserDisabledHdrTypesFromSettingsLocked(); updateUserPreferredDisplayModeSettingsLocked(); if (mIsHdrOutputControlEnabled) { updateHdrConversionModeSettingsLocked(); } + if (mFlags.isDisplayContentModeManagementEnabled()) { + updateMirrorBuiltInDisplaySettingLocked(); + } } mDisplayModeDirector.setDesiredDisplayModeSpecsListener( @@ -1180,27 +1191,61 @@ public final class DisplayManagerService extends SystemService { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor( Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED), false, this); + + if (mFlags.isDisplayContentModeManagementEnabled()) { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + MIRROR_BUILT_IN_DISPLAY), false, this, UserHandle.USER_ALL); + } } @Override public void onChange(boolean selfChange, Uri uri) { - handleSettingsChange(); + if (Settings.Secure.getUriFor( + Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED).equals(uri)) { + handleMinimalPostProcessingAllowedSettingChange(); + return; + } + + if (Settings.Secure.getUriFor(MIRROR_BUILT_IN_DISPLAY).equals(uri)) { + if (mFlags.isDisplayContentModeManagementEnabled()) { + updateMirrorBuiltInDisplaySettingLocked(); + } + return; + } } } - private void handleSettingsChange() { + private void handleMinimalPostProcessingAllowedSettingChange() { synchronized (mSyncRoot) { - updateSettingsLocked(); + updateMinimalPostProcessingAllowedSettingLocked(); scheduleTraversalLocked(false); } } - private void updateSettingsLocked() { + private void updateMinimalPostProcessingAllowedSettingLocked() { setMinimalPostProcessingAllowed(Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.MINIMAL_POST_PROCESSING_ALLOWED, 1, UserHandle.USER_CURRENT) != 0); } + private void updateMirrorBuiltInDisplaySettingLocked() { + if (!mFlags.isDisplayContentModeManagementEnabled()) { + Slog.e(TAG, "MirrorBuiltInDisplay setting shouldn't be updated when the flag is off."); + return; + } + + synchronized (mSyncRoot) { + ContentResolver resolver = mContext.getContentResolver(); + final boolean mirrorBuiltInDisplay = Settings.Secure.getIntForUser(resolver, + MIRROR_BUILT_IN_DISPLAY, 0, UserHandle.USER_CURRENT) != 0; + if (mMirrorBuiltInDisplay == mirrorBuiltInDisplay) { + return; + } + mMirrorBuiltInDisplay = mirrorBuiltInDisplay; + } + } + private void restoreResolutionFromBackup() { int savedMode = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.SCREEN_RESOLUTION_MODE, diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 45106f54cb9f..585fc44fa452 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -256,6 +256,10 @@ public class DisplayManagerFlags { Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS, Flags::displayListenerPerformanceImprovements ); + private final FlagState mEnableDisplayContentModeManagementFlagState = new FlagState( + Flags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, + Flags::enableDisplayContentModeManagement + ); private final FlagState mSubscribeGranularDisplayEvents = new FlagState( Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS, @@ -556,6 +560,10 @@ public class DisplayManagerFlags { return mDisplayListenerPerformanceImprovementsFlagState.isEnabled(); } + public boolean isDisplayContentModeManagementEnabled() { + return mEnableDisplayContentModeManagementFlagState.isEnabled(); + } + /** * @return {@code true} if the flag for subscribing to granular display events is enabled */ @@ -618,6 +626,7 @@ public class DisplayManagerFlags { pw.println(" " + mEnablePluginManagerFlagState); pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState); pw.println(" " + mSubscribeGranularDisplayEvents); + pw.println(" " + mEnableDisplayContentModeManagementFlagState); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/plugin/PluginEventStorage.java b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java new file mode 100644 index 000000000000..c58ba556bcb6 --- /dev/null +++ b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java @@ -0,0 +1,134 @@ +/* + * 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.server.display.plugin; + +import android.util.IndentingPrintWriter; + +import com.android.internal.util.RingBuffer; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +class PluginEventStorage { + private static final long TIME_FRAME_LENGTH = 60_000; + private static final long MIN_EVENT_DELAY = 500; + private static final int MAX_TIME_FRAMES = 10; + // not thread safe + private static final SimpleDateFormat sDateFormat = new SimpleDateFormat( + "MM-dd HH:mm:ss.SSS", Locale.US); + + RingBuffer<TimeFrame> mEvents = new RingBuffer<>( + TimeFrame::new, TimeFrame[]::new, MAX_TIME_FRAMES); + + private final Map<PluginType<?>, Long> mEventTimes = new HashMap<>(); + private long mTimeFrameStart = 0; + private final Map<PluginType<?>, EventCounter> mCounters = new HashMap<>(); + + <T> void onValueUpdated(PluginType<T> type) { + long eventTime = System.currentTimeMillis(); + if (eventTime - TIME_FRAME_LENGTH > mTimeFrameStart) { // event is in next TimeFrame + closeCurrentTimeFrame(); + mTimeFrameStart = eventTime; + } + updateCurrentTimeFrame(type, eventTime); + } + + private void closeCurrentTimeFrame() { + if (!mCounters.isEmpty()) { + mEvents.append(new TimeFrame( + mTimeFrameStart, mTimeFrameStart + TIME_FRAME_LENGTH, mCounters)); + mCounters.clear(); + } + } + + private <T> void updateCurrentTimeFrame(PluginType<T> type, long eventTime) { + EventCounter counter = mCounters.get(type); + long previousTimestamp = mEventTimes.getOrDefault(type, 0L); + if (counter == null) { + counter = new EventCounter(); + mCounters.put(type, counter); + } + counter.increase(eventTime, previousTimestamp); + mEventTimes.put(type, eventTime); + } + + List<TimeFrame> getTimeFrames() { + List<TimeFrame> timeFrames = new ArrayList<>(Arrays.stream(mEvents.toArray()).toList()); + timeFrames.add(new TimeFrame( + mTimeFrameStart, System.currentTimeMillis(), mCounters)); + return timeFrames; + } + + static class TimeFrame { + private final long mStart; + private final long mEnd; + private final Map<PluginType<?>, EventCounter> mCounters; + + private TimeFrame() { + this(0, 0, Map.of()); + } + + private TimeFrame(long start, long end, Map<PluginType<?>, EventCounter> counters) { + mStart = start; + mEnd = end; + mCounters = new HashMap<>(counters); + } + + @SuppressWarnings("JavaUtilDate") + void dump(PrintWriter pw) { + pw.append("TimeFrame:[") + .append(sDateFormat.format(new Date(mStart))) + .append(" - ") + .append(sDateFormat.format(new Date(mEnd))) + .println("]:"); + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + if (mCounters.isEmpty()) { + ipw.println("NO EVENTS"); + } else { + for (Map.Entry<PluginType<?>, EventCounter> entry: mCounters.entrySet()) { + ipw.append(entry.getKey().mName).append(" -> {"); + entry.getValue().dump(ipw); + ipw.println("}"); + } + } + } + } + + private static class EventCounter { + private int mEventCounter = 0; + private int mFastEventCounter = 0; + + private void increase(long timestamp, long previousTimestamp) { + mEventCounter++; + if (timestamp - previousTimestamp < MIN_EVENT_DELAY) { + mFastEventCounter++; + } + } + + private void dump(PrintWriter pw) { + pw.append("Count:").append(String.valueOf(mEventCounter)) + .append("; Fast:").append(String.valueOf(mFastEventCounter)); + } + } +} diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java index 2bcea777e681..dd3415fb614d 100644 --- a/services/core/java/com/android/server/display/plugin/PluginStorage.java +++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java @@ -25,6 +25,7 @@ import com.android.tools.r8.keepanno.annotations.KeepForApi; import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -39,6 +40,8 @@ public class PluginStorage { private final Map<PluginType<?>, Object> mValues = new HashMap<>(); @GuardedBy("mLock") private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>(); + @GuardedBy("mLock") + private final PluginEventStorage mPluginEventStorage = new PluginEventStorage(); /** * Updates value in storage and forwards it to corresponding listeners. @@ -50,6 +53,7 @@ public class PluginStorage { Set<PluginManager.PluginChangeListener<T>> localListeners; synchronized (mLock) { mValues.put(type, value); + mPluginEventStorage.onValueUpdated(type); ListenersContainer<T> container = getListenersContainerForTypeLocked(type); localListeners = new LinkedHashSet<>(container.mListeners); } @@ -91,13 +95,19 @@ public class PluginStorage { Map<PluginType<?>, Object> localValues; @SuppressWarnings("rawtypes") Map<PluginType, Set> localListeners = new HashMap<>(); + List<PluginEventStorage.TimeFrame> timeFrames; synchronized (mLock) { + timeFrames = mPluginEventStorage.getTimeFrames(); localValues = new HashMap<>(mValues); mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners)); } pw.println("PluginStorage:"); pw.println("values=" + localValues); pw.println("listeners=" + localListeners); + pw.println("PluginEventStorage:"); + for (PluginEventStorage.TimeFrame timeFrame: timeFrames) { + timeFrame.dump(pw); + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java index 7155cd0fc719..4a533f42ffea 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java @@ -27,6 +27,10 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -60,6 +64,20 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub /** True if this endpoint is registered with the service. */ private AtomicBoolean mIsRegistered = new AtomicBoolean(true); + private final Object mOpenSessionLock = new Object(); + + /** The set of session IDs that are pending remote acceptance */ + @GuardedBy("mOpenSessionLock") + private final Set<Integer> mPendingSessionIds = new HashSet<>(); + + /** The set of session IDs that are actively enabled by this endpoint */ + @GuardedBy("mOpenSessionLock") + private final Set<Integer> mActiveSessionIds = new HashSet<>(); + + /** The set of session IDs that are actively enabled by the remote endpoint */ + @GuardedBy("mOpenSessionLock") + private final Set<Integer> mActiveRemoteSessionIds = new HashSet<>(); + /* package */ ContextHubEndpointBroker( Context context, IContextHubWrapper contextHubProxy, @@ -86,16 +104,24 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub if (!mIsRegistered.get()) throw new IllegalStateException("Endpoint is not registered"); int sessionId = mEndpointManager.reserveSessionId(); EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination); - try { - mContextHubProxy.openEndpointSession( - sessionId, halEndpointInfo.id, mHalEndpointInfo.id, serviceDescriptor); - } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { - Log.e(TAG, "Exception while calling HAL openEndpointSession", e); - mEndpointManager.returnSessionId(sessionId); - throw e; - } - return sessionId; + synchronized (mOpenSessionLock) { + try { + mPendingSessionIds.add(sessionId); + mContextHubProxy.openEndpointSession( + sessionId, + halEndpointInfo.id, + mHalEndpointInfo.id, + serviceDescriptor); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + Log.e(TAG, "Exception while calling HAL openEndpointSession", e); + mPendingSessionIds.remove(sessionId); + mEndpointManager.returnSessionId(sessionId); + throw e; + } + + return sessionId; + } } @Override @@ -111,11 +137,6 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } @Override - public void openSessionRequestComplete(int sessionId) { - // TODO(b/378487799): Implement this - } - - @Override public void unregister() { ContextHubServiceUtil.checkPermissions(mContext); mIsRegistered.set(false); @@ -124,11 +145,34 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub } catch (RemoteException e) { Log.e(TAG, "RemoteException while calling HAL unregisterEndpoint", e); } - // TODO(b/378487799): Release reserved session IDs + synchronized (mOpenSessionLock) { + for (int id : mPendingSessionIds) { + mEndpointManager.returnSessionId(id); + } + for (int id : mActiveSessionIds) { + mEndpointManager.returnSessionId(id); + } + mPendingSessionIds.clear(); + mActiveSessionIds.clear(); + mActiveRemoteSessionIds.clear(); + } mEndpointManager.unregisterEndpoint(mEndpointInfo.getIdentifier().getEndpoint()); } @Override + public void openSessionRequestComplete(int sessionId) { + ContextHubServiceUtil.checkPermissions(mContext); + synchronized (mOpenSessionLock) { + try { + mContextHubProxy.endpointSessionOpenComplete(sessionId); + mActiveRemoteSessionIds.add(sessionId); + } catch (RemoteException | IllegalArgumentException | UnsupportedOperationException e) { + Log.e(TAG, "Exception while calling endpointSessionOpenComplete", e); + } + } + } + + @Override public void sendMessage( int sessionId, HubMessage message, IContextHubTransactionCallback callback) { // TODO(b/381102453): Implement this @@ -150,4 +194,53 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub mContextHubEndpointCallback.asBinder().linkToDeath(this, 0 /* flags */); } } + + /* package */ void onEndpointSessionOpenRequest( + int sessionId, HubEndpointInfo initiator, String serviceDescriptor) { + if (mContextHubEndpointCallback != null) { + try { + mContextHubEndpointCallback.onSessionOpenRequest( + sessionId, initiator, serviceDescriptor); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onSessionOpenRequest", e); + } + } + } + + /* package */ void onCloseEndpointSession(int sessionId, byte reason) { + synchronized (mOpenSessionLock) { + mPendingSessionIds.remove(sessionId); + mActiveSessionIds.remove(sessionId); + mActiveRemoteSessionIds.remove(sessionId); + } + if (mContextHubEndpointCallback != null) { + try { + mContextHubEndpointCallback.onSessionClosed(sessionId, reason); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onSessionClosed", e); + } + } + } + + /* package */ void onEndpointSessionOpenComplete(int sessionId) { + synchronized (mOpenSessionLock) { + mPendingSessionIds.remove(sessionId); + mActiveSessionIds.add(sessionId); + } + if (mContextHubEndpointCallback != null) { + try { + mContextHubEndpointCallback.onSessionOpenComplete(sessionId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onSessionClosed", e); + } + } + } + + /* package */ boolean hasSessionId(int sessionId) { + synchronized (mOpenSessionLock) { + return mPendingSessionIds.contains(sessionId) + || mActiveSessionIds.contains(sessionId) + || mActiveRemoteSessionIds.contains(sessionId); + } + } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java index fb64f99e5904..8c5095f35f0d 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointManager.java @@ -38,7 +38,8 @@ import java.util.concurrent.ConcurrentHashMap; * * @hide */ -/* package */ class ContextHubEndpointManager { +/* package */ class ContextHubEndpointManager + implements ContextHubHalEndpointCallback.IEndpointSessionCallback { private static final String TAG = "ContextHubEndpointManager"; /** The hub ID of the Context Hub Service. */ @@ -56,6 +57,8 @@ import java.util.concurrent.ConcurrentHashMap; /** The proxy to talk to the Context Hub. */ private final IContextHubWrapper mContextHubProxy; + private final HubInfoRegistry mHubInfoRegistry; + /** A map of endpoint IDs to brokers currently registered. */ private final Map<Long, ContextHubEndpointBroker> mEndpointMap = new ConcurrentHashMap<>(); @@ -85,9 +88,11 @@ import java.util.concurrent.ConcurrentHashMap; /** Initialized to true if all initialization in the constructor succeeds. */ private final boolean mSessionIdsValid; - /* package */ ContextHubEndpointManager(Context context, IContextHubWrapper contextHubProxy) { + /* package */ ContextHubEndpointManager( + Context context, IContextHubWrapper contextHubProxy, HubInfoRegistry hubInfoRegistry) { mContext = context; mContextHubProxy = contextHubProxy; + mHubInfoRegistry = hubInfoRegistry; int[] range = null; try { range = mContextHubProxy.requestSessionIdRange(SERVICE_SESSION_RANGE); @@ -210,6 +215,70 @@ import java.util.concurrent.ConcurrentHashMap; mEndpointMap.remove(endpointId); } + @Override + public void onEndpointSessionOpenRequest( + int sessionId, + HubEndpointInfo.HubEndpointIdentifier destination, + HubEndpointInfo.HubEndpointIdentifier initiator, + String serviceDescriptor) { + if (destination.getHub() != SERVICE_HUB_ID) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: invalid destination hub ID: " + + destination.getHub()); + return; + } + ContextHubEndpointBroker broker = mEndpointMap.get(destination.getEndpoint()); + if (broker == null) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: unknown destination endpoint ID: " + + destination.getEndpoint()); + return; + } + HubEndpointInfo initiatorInfo = mHubInfoRegistry.getEndpointInfo(initiator); + if (initiatorInfo == null) { + Log.e( + TAG, + "onEndpointSessionOpenRequest: unknown initiator endpoint ID: " + + initiator.getEndpoint()); + return; + } + broker.onEndpointSessionOpenRequest(sessionId, initiatorInfo, serviceDescriptor); + } + + @Override + public void onCloseEndpointSession(int sessionId, byte reason) { + boolean callbackInvoked = false; + for (ContextHubEndpointBroker broker : mEndpointMap.values()) { + if (broker.hasSessionId(sessionId)) { + broker.onCloseEndpointSession(sessionId, reason); + callbackInvoked = true; + break; + } + } + + if (!callbackInvoked) { + Log.w(TAG, "onCloseEndpointSession: unknown session ID " + sessionId); + } + } + + @Override + public void onEndpointSessionOpenComplete(int sessionId) { + boolean callbackInvoked = false; + for (ContextHubEndpointBroker broker : mEndpointMap.values()) { + if (broker.hasSessionId(sessionId)) { + broker.onEndpointSessionOpenComplete(sessionId); + callbackInvoked = true; + break; + } + } + + if (!callbackInvoked) { + Log.w(TAG, "onEndpointSessionOpenComplete: unknown session ID " + sessionId); + } + } + /** @return an available endpoint ID */ private long getNewEndpointId() { synchronized (mEndpointLock) { @@ -220,6 +289,9 @@ import java.util.concurrent.ConcurrentHashMap; } } + /** + * @return true if the provided session ID range is valid + */ private boolean isSessionIdRangeValid(int minId, int maxId) { return (minId <= maxId) && (minId >= 0) && (maxId >= 0); } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java index c05f7a0c0e00..9d52c6a020f4 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java @@ -26,6 +26,7 @@ import android.os.RemoteException; public class ContextHubHalEndpointCallback extends android.hardware.contexthub.IEndpointCallback.Stub { private final IEndpointLifecycleCallback mEndpointLifecycleCallback; + private final IEndpointSessionCallback mEndpointSessionCallback; /** Interface for listening for endpoint start and stop events. */ public interface IEndpointLifecycleCallback { @@ -36,8 +37,27 @@ public class ContextHubHalEndpointCallback void onEndpointStopped(HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason); } - ContextHubHalEndpointCallback(IEndpointLifecycleCallback endpointLifecycleCallback) { + /** Interface for listening for endpoint session events. */ + public interface IEndpointSessionCallback { + /** Called when an endpoint session open is requested by the HAL. */ + void onEndpointSessionOpenRequest( + int sessionId, + HubEndpointInfo.HubEndpointIdentifier destinationId, + HubEndpointInfo.HubEndpointIdentifier initiatorId, + String serviceDescriptor); + + /** Called when a endpoint close session is completed. */ + void onCloseEndpointSession(int sessionId, byte reason); + + /** Called when a requested endpoint open session is completed */ + void onEndpointSessionOpenComplete(int sessionId); + } + + ContextHubHalEndpointCallback( + IEndpointLifecycleCallback endpointLifecycleCallback, + IEndpointSessionCallback endpointSessionCallback) { mEndpointLifecycleCallback = endpointLifecycleCallback; + mEndpointSessionCallback = endpointSessionCallback; } @Override @@ -48,7 +68,7 @@ public class ContextHubHalEndpointCallback } HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length]; for (int i = 0; i < halEndpointInfos.length; i++) { - endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]); + endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]); } mEndpointLifecycleCallback.onEndpointStarted(endpointInfos); } @@ -72,14 +92,23 @@ public class ContextHubHalEndpointCallback @Override public void onEndpointSessionOpenRequest( - int i, EndpointId endpointId, EndpointId endpointId1, String s) - throws RemoteException {} + int i, EndpointId destination, EndpointId initiator, String s) throws RemoteException { + HubEndpointInfo.HubEndpointIdentifier destinationId = + new HubEndpointInfo.HubEndpointIdentifier(destination.hubId, destination.id); + HubEndpointInfo.HubEndpointIdentifier initiatorId = + new HubEndpointInfo.HubEndpointIdentifier(initiator.hubId, initiator.id); + mEndpointSessionCallback.onEndpointSessionOpenRequest(i, destinationId, initiatorId, s); + } @Override - public void onCloseEndpointSession(int i, byte b) throws RemoteException {} + public void onCloseEndpointSession(int i, byte b) throws RemoteException { + mEndpointSessionCallback.onCloseEndpointSession(i, b); + } @Override - public void onEndpointSessionOpenComplete(int i) throws RemoteException {} + public void onEndpointSessionOpenComplete(int i) throws RemoteException { + mEndpointSessionCallback.onEndpointSessionOpenComplete(i); + } @Override public int getInterfaceVersion() throws RemoteException { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 0d926b99217d..d916eda693d8 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -333,7 +333,8 @@ public class ContextHubService extends IContextHubService.Stub { HubInfoRegistry registry; try { registry = new HubInfoRegistry(mContextHubWrapper); - mEndpointManager = new ContextHubEndpointManager(mContext, mContextHubWrapper); + mEndpointManager = + new ContextHubEndpointManager(mContext, mContextHubWrapper, registry); Log.i(TAG, "Enabling generic offload API"); } catch (UnsupportedOperationException e) { mEndpointManager = null; @@ -533,7 +534,7 @@ public class ContextHubService extends IContextHubService.Stub { } try { mContextHubWrapper.registerEndpointCallback( - new ContextHubHalEndpointCallback(mHubInfoRegistry)); + new ContextHubHalEndpointCallback(mHubInfoRegistry, mEndpointManager)); } catch (RemoteException | UnsupportedOperationException e) { Log.e(TAG, "Exception while registering IEndpointCallback", e); } @@ -797,7 +798,7 @@ public class ContextHubService extends IContextHubService.Stub { throws RemoteException { super.registerEndpoint_enforcePermission(); if (mEndpointManager == null) { - Log.e(TAG, "ContextHubService.registerEndpoint: endpoint manager failed to initialize"); + Log.e(TAG, "Endpoint manager failed to initialize"); throw new UnsupportedOperationException("Endpoint registration is not supported"); } return mEndpointManager.registerEndpoint(pendingHubEndpointInfo, callback); @@ -808,7 +809,8 @@ public class ContextHubService extends IContextHubService.Stub { public void registerEndpointDiscoveryCallbackId( long endpointId, IContextHubEndpointDiscoveryCallback callback) throws RemoteException { super.registerEndpointDiscoveryCallbackId_enforcePermission(); - // TODO(b/375487784): Implement this + checkEndpointDiscoveryPreconditions(); + mHubInfoRegistry.registerEndpointDiscoveryCallback(endpointId, callback); } @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @@ -817,7 +819,8 @@ public class ContextHubService extends IContextHubService.Stub { String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback) throws RemoteException { super.registerEndpointDiscoveryCallbackDescriptor_enforcePermission(); - // TODO(b/375487784): Implement this + checkEndpointDiscoveryPreconditions(); + mHubInfoRegistry.registerEndpointDiscoveryCallback(serviceDescriptor, callback); } @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @@ -825,7 +828,15 @@ public class ContextHubService extends IContextHubService.Stub { public void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback) throws RemoteException { super.unregisterEndpointDiscoveryCallback_enforcePermission(); - // TODO(b/375487784): Implement this + checkEndpointDiscoveryPreconditions(); + mHubInfoRegistry.unregisterEndpointDiscoveryCallback(callback); + } + + private void checkEndpointDiscoveryPreconditions() { + if (mHubInfoRegistry == null) { + Log.e(TAG, "Hub endpoint registry failed to initialize"); + throw new UnsupportedOperationException("Endpoint discovery is not supported"); + } } /** diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java index d2b2331d54f3..6f5f191849e2 100644 --- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java +++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java @@ -18,7 +18,9 @@ package com.android.server.location.contexthub; import android.hardware.contexthub.HubEndpointInfo; import android.hardware.contexthub.HubServiceInfo; +import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback; import android.hardware.location.HubInfo; +import android.os.DeadObjectException; import android.os.RemoteException; import android.util.ArrayMap; import android.util.IndentingPrintWriter; @@ -29,6 +31,9 @@ import com.android.internal.annotations.GuardedBy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.BiConsumer; class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycleCallback { private static final String TAG = "HubInfoRegistry"; @@ -43,6 +48,56 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl private final ArrayMap<HubEndpointInfo.HubEndpointIdentifier, HubEndpointInfo> mHubEndpointInfos = new ArrayMap<>(); + /** + * A wrapper class that is used to store arguments to + * ContextHubManager.registerEndpointCallback. + */ + private static class DiscoveryCallback { + private final IContextHubEndpointDiscoveryCallback mCallback; + private final Optional<Long> mEndpointId; + private final Optional<String> mServiceDescriptor; + + DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, long endpointId) { + mCallback = callback; + mEndpointId = Optional.of(endpointId); + mServiceDescriptor = Optional.empty(); + } + + DiscoveryCallback(IContextHubEndpointDiscoveryCallback callback, String serviceDescriptor) { + mCallback = callback; + mEndpointId = Optional.empty(); + mServiceDescriptor = Optional.of(serviceDescriptor); + } + + public IContextHubEndpointDiscoveryCallback getCallback() { + return mCallback; + } + + /** + * @param info The hub endpoint info to check + * @return true if info matches + */ + public boolean isMatch(HubEndpointInfo info) { + if (mEndpointId.isPresent()) { + return mEndpointId.get() == info.getIdentifier().getEndpoint(); + } + if (mServiceDescriptor.isPresent()) { + for (HubServiceInfo serviceInfo : info.getServiceInfoCollection()) { + if (mServiceDescriptor.get().equals(serviceInfo.getServiceDescriptor())) { + return true; + } + } + } + return false; + } + } + + /* The list of discovery callbacks registered with the service */ + @GuardedBy("mCallbackLock") + private final List<DiscoveryCallback> mEndpointDiscoveryCallbacks = new ArrayList<>(); + + private final Object mCallbackLock = new Object(); + HubInfoRegistry(IContextHubWrapper contextHubWrapper) { mContextHubWrapper = contextHubWrapper; refreshCachedHubs(); @@ -87,6 +142,12 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl } } + public HubEndpointInfo getEndpointInfo(HubEndpointInfo.HubEndpointIdentifier id) { + synchronized (mLock) { + return mHubEndpointInfos.get(id); + } + } + /** Invoked when HAL restarts */ public void onHalRestart() { synchronized (mLock) { @@ -103,16 +164,50 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl mHubEndpointInfos.put(endpointInfo.getIdentifier(), endpointInfo); } } + + invokeForMatchingEndpoints( + endpointInfos, + (cb, infoList) -> { + try { + cb.onEndpointsStarted(infoList); + } catch (RemoteException e) { + if (e instanceof DeadObjectException) { + Log.w(TAG, "onEndpointStarted: callback died, unregistering"); + unregisterEndpointDiscoveryCallback(cb); + } else { + Log.e(TAG, "Exception while calling onEndpointsStarted", e); + } + } + }); } @Override public void onEndpointStopped( HubEndpointInfo.HubEndpointIdentifier[] endpointIds, byte reason) { + ArrayList<HubEndpointInfo> removedInfoList = new ArrayList<>(); synchronized (mLock) { for (HubEndpointInfo.HubEndpointIdentifier endpointId : endpointIds) { - mHubEndpointInfos.remove(endpointId); + HubEndpointInfo info = mHubEndpointInfos.remove(endpointId); + if (info != null) { + removedInfoList.add(info); + } } } + + invokeForMatchingEndpoints( + removedInfoList.toArray(new HubEndpointInfo[removedInfoList.size()]), + (cb, infoList) -> { + try { + cb.onEndpointsStopped(infoList, reason); + } catch (RemoteException e) { + if (e instanceof DeadObjectException) { + Log.w(TAG, "onEndpointStopped: callback died, unregistering"); + unregisterEndpointDiscoveryCallback(cb); + } else { + Log.e(TAG, "Exception while calling onEndpointsStopped", e); + } + } + }); } /** Return a list of {@link HubEndpointInfo} that represents endpoints with the matching id. */ @@ -145,6 +240,77 @@ class HubInfoRegistry implements ContextHubHalEndpointCallback.IEndpointLifecycl return searchResult; } + /* package */ + void registerEndpointDiscoveryCallback( + long endpointId, IContextHubEndpointDiscoveryCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); + synchronized (mCallbackLock) { + checkCallbackAlreadyRegistered(callback); + mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, endpointId)); + } + } + + /* package */ + void registerEndpointDiscoveryCallback( + String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); + synchronized (mCallbackLock) { + checkCallbackAlreadyRegistered(callback); + mEndpointDiscoveryCallbacks.add(new DiscoveryCallback(callback, serviceDescriptor)); + } + } + + /* package */ + void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback) { + Objects.requireNonNull(callback, "callback cannot be null"); + synchronized (mCallbackLock) { + for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) { + if (discoveryCallback.getCallback().asBinder() == callback.asBinder()) { + mEndpointDiscoveryCallbacks.remove(discoveryCallback); + break; + } + } + } + } + + private void checkCallbackAlreadyRegistered( + IContextHubEndpointDiscoveryCallback callback) { + synchronized (mCallbackLock) { + for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) { + if (discoveryCallback.mCallback.asBinder() == callback.asBinder()) { + throw new IllegalArgumentException("Callback is already registered"); + } + } + } + } + + /** + * Iterates through all registered discovery callbacks and invokes a given callback for those + * that match the endpoints the callback is targeted for. + * + * @param endpointInfos The list of endpoint infos to check for a match. + * @param consumer The callback to invoke, which consumes the callback object and the list of + * matched endpoint infos. + */ + private void invokeForMatchingEndpoints( + HubEndpointInfo[] endpointInfos, + BiConsumer<IContextHubEndpointDiscoveryCallback, HubEndpointInfo[]> consumer) { + synchronized (mCallbackLock) { + for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) { + ArrayList<HubEndpointInfo> infoList = new ArrayList<>(); + for (HubEndpointInfo endpointInfo : endpointInfos) { + if (discoveryCallback.isMatch(endpointInfo)) { + infoList.add(endpointInfo); + } + } + + consumer.accept( + discoveryCallback.getCallback(), + infoList.toArray(new HubEndpointInfo[infoList.size()])); + } + } + } + void dump(IndentingPrintWriter ipw) { synchronized (mLock) { dumpLocked(ipw); diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index c79dc84ec2af..6cb942980403 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -261,6 +261,9 @@ public abstract class IContextHubWrapper { public void unregisterEndpoint(android.hardware.contexthub.EndpointInfo info) throws RemoteException {} + /** Notifies the completion of a session opened by the HAL */ + public void endpointSessionOpenComplete(int sessionId) throws RemoteException {} + /** * @return True if this version of the Contexthub HAL supports Location setting notifications. */ @@ -745,6 +748,15 @@ public abstract class IContextHubWrapper { hub.unregisterEndpoint(info); } + @Override + public void endpointSessionOpenComplete(int sessionId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + hub.endpointSessionOpenComplete(sessionId); + } + public boolean supportsLocationSettingNotifications() { return true; } diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 849751b99aae..1673b8e6a0af 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -32,6 +32,7 @@ import android.media.quality.PictureProfileHandle; import android.media.quality.SoundProfile; import android.media.quality.SoundProfileHandle; import android.os.PersistableBundle; +import android.os.UserHandle; import android.util.Log; import com.android.server.SystemService; @@ -81,7 +82,7 @@ public class MediaQualityService extends SystemService { private final class BinderService extends IMediaQualityManager.Stub { @Override - public PictureProfile createPictureProfile(PictureProfile pp, int userId) { + public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -100,12 +101,12 @@ public class MediaQualityService extends SystemService { } @Override - public void updatePictureProfile(String id, PictureProfile pp, int userId) { + public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { // TODO: implement } @Override - public void removePictureProfile(String id, int userId) { + public void removePictureProfile(String id, UserHandle user) { Long intId = mPictureProfileTempIdMap.inverse().get(id); if (intId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); @@ -118,7 +119,8 @@ public class MediaQualityService extends SystemService { } @Override - public PictureProfile getPictureProfile(int type, String name, int userId) { + public PictureProfile getPictureProfile(int type, String name, boolean includeParams, + UserHandle user) { String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " + BaseParameters.PARAMETER_NAME + " = ?"; String[] selectionArguments = {Integer.toString(type), name}; @@ -144,7 +146,8 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getPictureProfilesByPackage(String packageName, int userId) { + public List<PictureProfile> getPictureProfilesByPackage( + String packageName, boolean includeParams, UserHandle user) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return getPictureProfilesBasedOnConditions(getAllMediaProfileColumns(), selection, @@ -152,18 +155,19 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getAvailablePictureProfiles(int userId) { + public List<PictureProfile> getAvailablePictureProfiles( + boolean includeParams, UserHandle user) { return new ArrayList<>(); } @Override - public boolean setDefaultPictureProfile(String profileId, int userId) { + public boolean setDefaultPictureProfile(String profileId, UserHandle user) { // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override - public List<String> getPictureProfilePackageNames(int userId) { + public List<String> getPictureProfilePackageNames(UserHandle user) { String [] column = {BaseParameters.PARAMETER_PACKAGE}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); @@ -174,17 +178,17 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfileHandle> getPictureProfileHandle(String[] id, int userId) { + public List<PictureProfileHandle> getPictureProfileHandle(String[] id, UserHandle user) { return new ArrayList<>(); } @Override - public List<SoundProfileHandle> getSoundProfileHandle(String[] id, int userId) { + public List<SoundProfileHandle> getSoundProfileHandle(String[] id, UserHandle user) { return new ArrayList<>(); } @Override - public SoundProfile createSoundProfile(SoundProfile sp, int userId) { + public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); @@ -203,12 +207,12 @@ public class MediaQualityService extends SystemService { } @Override - public void updateSoundProfile(String id, SoundProfile pp, int userId) { + public void updateSoundProfile(String id, SoundProfile pp, UserHandle user) { // TODO: implement } @Override - public void removeSoundProfile(String id, int userId) { + public void removeSoundProfile(String id, UserHandle user) { Long intId = mSoundProfileTempIdMap.inverse().get(id); if (intId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); @@ -221,9 +225,10 @@ public class MediaQualityService extends SystemService { } @Override - public SoundProfile getSoundProfile(int type, String id, int userId) { + public SoundProfile getSoundProfile(int type, String id, boolean includeParams, + UserHandle user) { String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " - + BaseParameters.PARAMETER_NAME + " = ?"; + + BaseParameters.PARAMETER_ID + " = ?"; String[] selectionArguments = {String.valueOf(type), id}; try ( @@ -247,7 +252,8 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getSoundProfilesByPackage(String packageName, int userId) { + public List<SoundProfile> getSoundProfilesByPackage( + String packageName, boolean includeParams, UserHandle user) { String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; String[] selectionArguments = {packageName}; return getSoundProfilesBasedOnConditions(getAllMediaProfileColumns(), selection, @@ -255,18 +261,19 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getAvailableSoundProfiles(int userId) { + public List<SoundProfile> getAvailableSoundProfiles( + boolean includeParams, UserHandle user) { return new ArrayList<>(); } @Override - public boolean setDefaultSoundProfile(String profileId, int userId) { + public boolean setDefaultSoundProfile(String profileId, UserHandle user) { // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override - public List<String> getSoundProfilePackageNames(int userId) { + public List<String> getSoundProfilePackageNames(UserHandle user) { String [] column = {BaseParameters.PARAMETER_NAME}; List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column, null, null); @@ -456,70 +463,71 @@ public class MediaQualityService extends SystemService { } @Override - public void setAmbientBacklightSettings(AmbientBacklightSettings settings, int userId) { + public void setAmbientBacklightSettings( + AmbientBacklightSettings settings, UserHandle user) { } @Override - public void setAmbientBacklightEnabled(boolean enabled, int userId) { + public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { } @Override - public List<ParamCapability> getParamCapabilities(List<String> names, int userId) { + public List<ParamCapability> getParamCapabilities(List<String> names, UserHandle user) { return new ArrayList<>(); } @Override - public List<String> getPictureProfileAllowList(int userId) { + public List<String> getPictureProfileAllowList(UserHandle user) { return new ArrayList<>(); } @Override - public void setPictureProfileAllowList(List<String> packages, int userId) { + public void setPictureProfileAllowList(List<String> packages, UserHandle user) { } @Override - public List<String> getSoundProfileAllowList(int userId) { + public List<String> getSoundProfileAllowList(UserHandle user) { return new ArrayList<>(); } @Override - public void setSoundProfileAllowList(List<String> packages, int userId) { + public void setSoundProfileAllowList(List<String> packages, UserHandle user) { } @Override - public boolean isSupported(int userId) { + public boolean isSupported(UserHandle user) { return false; } @Override - public void setAutoPictureQualityEnabled(boolean enabled, int userId) { + public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) { } @Override - public boolean isAutoPictureQualityEnabled(int userId) { + public boolean isAutoPictureQualityEnabled(UserHandle user) { return false; } @Override - public void setSuperResolutionEnabled(boolean enabled, int userId) { + public void setSuperResolutionEnabled(boolean enabled, UserHandle user) { } @Override - public boolean isSuperResolutionEnabled(int userId) { + public boolean isSuperResolutionEnabled(UserHandle user) { return false; } @Override - public void setAutoSoundQualityEnabled(boolean enabled, int userId) { + public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) { } @Override - public boolean isAutoSoundQualityEnabled(int userId) { + public boolean isAutoSoundQualityEnabled(UserHandle user) { return false; } @Override - public boolean isAmbientBacklightEnabled(int userId) { + public boolean isAmbientBacklightEnabled(UserHandle user) { return false; } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 15af36ba66af..39eea740a902 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2594,6 +2594,7 @@ public class NotificationManagerService extends SystemService { intent.setPackage(pkg); intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, id); intent.putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, status); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); getContext().sendBroadcastAsUser(intent, UserHandle.of(userId)); }); } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index dc173b124884..95aff5652bb6 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -112,9 +112,10 @@ import android.util.SparseArray; import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; +import androidx.annotation.VisibleForTesting; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.FrameworkStatsLog; @@ -279,6 +280,11 @@ public class ZenModeHelper { mCallbacks.remove(callback); } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public List<Callback> getCallbacks() { + return mCallbacks; + } + public void initZenMode() { if (DEBUG) Log.d(TAG, "initZenMode"); synchronized (mConfigLock) { diff --git a/services/core/java/com/android/server/ondeviceintelligence/OWNERS b/services/core/java/com/android/server/ondeviceintelligence/OWNERS deleted file mode 100644 index 09774f78d712..000000000000 --- a/services/core/java/com/android/server/ondeviceintelligence/OWNERS +++ /dev/null @@ -1 +0,0 @@ -file:/core/java/android/app/ondeviceintelligence/OWNERS diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java index 8ec716077f46..871d12ee6394 100644 --- a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java +++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java @@ -20,116 +20,100 @@ import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION; import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.PermissionManuallyEnforced; +import android.annotation.RequiresPermission; +import android.app.ActivityManagerInternal; import android.content.Context; +import android.os.RemoteException; import android.os.instrumentation.ExecutableMethodFileOffsets; import android.os.instrumentation.IDynamicInstrumentationManager; +import android.os.instrumentation.IOffsetCallback; import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.MethodDescriptorParser; import android.os.instrumentation.TargetProcess; -import com.android.internal.annotations.VisibleForTesting; + +import com.android.server.LocalServices; import com.android.server.SystemService; import dalvik.system.VMDebug; import java.lang.reflect.Method; +import java.util.NoSuchElementException; +import java.util.Objects; + /** * System private implementation of the {@link IDynamicInstrumentationManager interface}. */ public class DynamicInstrumentationManagerService extends SystemService { + + private ActivityManagerInternal mAmInternal; + public DynamicInstrumentationManagerService(@NonNull Context context) { super(context); } @Override public void onStart() { + mAmInternal = LocalServices.getService(ActivityManagerInternal.class); publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService()); } private final class BinderService extends IDynamicInstrumentationManager.Stub { @Override @PermissionManuallyEnforced - public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets( - @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) { + @RequiresPermission(value = android.Manifest.permission.DYNAMIC_INSTRUMENTATION) + public void getExecutableMethodFileOffsets( + @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor, + @NonNull IOffsetCallback callback) { if (!com.android.art.flags.Flags.executableMethodFileOffsets()) { throw new UnsupportedOperationException(); } getContext().enforceCallingOrSelfPermission( DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission"); + Objects.requireNonNull(targetProcess.processName); - if (targetProcess.processName == null - || !targetProcess.processName.equals("system_server")) { - throw new UnsupportedOperationException( - "system_server is the only supported target process"); + if (!targetProcess.processName.equals("system_server")) { + try { + mAmInternal.getExecutableMethodFileOffsets(targetProcess.processName, + targetProcess.pid, targetProcess.uid, methodDescriptor, + new IOffsetCallback.Stub() { + @Override + public void onResult(ExecutableMethodFileOffsets result) { + try { + callback.onResult(result); + } catch (RemoteException e) { + /* ignore */ + } + } + }); + return; + } catch (NoSuchElementException e) { + throw new IllegalArgumentException( + "The specified app process cannot be found." , e); + } } - Method method = parseMethodDescriptor( + Method method = MethodDescriptorParser.parseMethodDescriptor( getClass().getClassLoader(), methodDescriptor); VMDebug.ExecutableMethodFileOffsets location = VMDebug.getExecutableMethodFileOffsets(method); - if (location == null) { - return null; - } - - ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets(); - ret.containerPath = location.getContainerPath(); - ret.containerOffset = location.getContainerOffset(); - ret.methodOffset = location.getMethodOffset(); - return ret; - } - } - - @VisibleForTesting - static Method parseMethodDescriptor(ClassLoader classLoader, - @NonNull MethodDescriptor descriptor) { - try { - Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName); - Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length]; - for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) { - String typeName = descriptor.fullyQualifiedParameters[i]; - boolean isArrayType = typeName.endsWith("[]"); - if (isArrayType) { - typeName = typeName.substring(0, typeName.length() - 2); + try { + if (location == null) { + callback.onResult(null); + return; } - switch (typeName) { - case "boolean": - parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class; - break; - case "byte": - parameters[i] = isArrayType ? byte.class.arrayType() : byte.class; - break; - case "char": - parameters[i] = isArrayType ? char.class.arrayType() : char.class; - break; - case "short": - parameters[i] = isArrayType ? short.class.arrayType() : short.class; - break; - case "int": - parameters[i] = isArrayType ? int.class.arrayType() : int.class; - break; - case "long": - parameters[i] = isArrayType ? long.class.arrayType() : long.class; - break; - case "float": - parameters[i] = isArrayType ? float.class.arrayType() : float.class; - break; - case "double": - parameters[i] = isArrayType ? double.class.arrayType() : double.class; - break; - default: - parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType() - : classLoader.loadClass(typeName); - } - } - return javaClass.getDeclaredMethod(descriptor.methodName, parameters); - } catch (ClassNotFoundException | NoSuchMethodException e) { - throw new IllegalArgumentException( - "The specified method cannot be found. Is this descriptor valid? " - + descriptor, e); + ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets(); + ret.containerPath = location.getContainerPath(); + ret.containerOffset = location.getContainerOffset(); + ret.methodOffset = location.getMethodOffset(); + callback.onResult(ret); + } catch (RemoteException e) { + throw new RuntimeException("Failed to invoke result callback", e); + } } } } diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 6d54be84d5e5..d78e98bf8e61 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -78,6 +78,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.function.BiFunction; +import java.util.function.Supplier; /** * Helper class to send broadcasts for various situations. @@ -216,7 +217,7 @@ public final class BroadcastHelper { filterExtrasForReceiver, bOptions); } - void sendResourcesChangedBroadcast(@NonNull Computer snapshot, + void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotSupplier, boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames, @@ -236,7 +237,7 @@ public final class BroadcastHelper { null /* targetPkg */, null /* finishedReceiver */, null /* userIds */, null /* instantUserIds */, null /* broadcastAllowList */, (callingUid, intentExtras) -> filterExtrasChangedPackageList( - snapshot, callingUid, intentExtras), + snapshotSupplier, callingUid, intentExtras), null /* bOptions */, null /* requiredPermissions */); } @@ -544,7 +545,7 @@ public final class BroadcastHelper { }); } - void sendPostInstallBroadcasts(@NonNull Computer snapshot, + void sendPostInstallBroadcasts(@NonNull Supplier<Computer> snapshotSupplier, @NonNull InstallRequest request, @NonNull String packageName, @NonNull String requiredPermissionControllerPackage, @@ -567,8 +568,8 @@ public final class BroadcastHelper { final int[] uids = new int[]{request.getRemovedInfo().mUid}; notifyResourcesChanged( false /* mediaStatus */, true /* replacing */, pkgNames, uids); - sendResourcesChangedBroadcast( - snapshot, false /* mediaStatus */, true /* replacing */, pkgNames, uids); + sendResourcesChangedBroadcast(snapshotSupplier, + false /* mediaStatus */, true /* replacing */, pkgNames, uids); } sendPackageRemovedBroadcasts( request.getRemovedInfo(), packageSender, isKillApp, false /*removedBySystem*/, @@ -608,6 +609,7 @@ public final class BroadcastHelper { null /* broadcastAllowList */, null); } + final Computer snapshot = snapshotSupplier.get(); // Send installed broadcasts if the package is not a static shared lib. if (staticSharedLibraryName == null) { // Send PACKAGE_ADDED broadcast for users that see the package for the first time @@ -732,7 +734,7 @@ public final class BroadcastHelper { if (!isArchived) { final String[] pkgNames = new String[]{packageName}; final int[] uids = new int[]{request.getPkg().getUid()}; - sendResourcesChangedBroadcast(snapshot, + sendResourcesChangedBroadcast(snapshotSupplier, true /* mediaStatus */, true /* replacing */, pkgNames, uids); notifyResourcesChanged(true /* mediaStatus */, true /* replacing */, pkgNames, uids); @@ -860,8 +862,8 @@ public final class BroadcastHelper { * access all the packages in the extras. */ @Nullable - private static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid, - @NonNull Bundle extras) { + private static Bundle filterExtrasChangedPackageList( + @NonNull Supplier<Computer> snapshotSupplier, int callingUid, @NonNull Bundle extras) { if (UserHandle.isCore(callingUid)) { // see all return extras; @@ -873,6 +875,7 @@ public final class BroadcastHelper { final int userId = extras.getInt( Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid)); final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST); + final Computer snapshot = snapshotSupplier.get(); final Pair<String[], int[]> filteredPkgs = filterPackages(snapshot, pkgs, uids, callingUid, userId); if (ArrayUtils.isEmpty(filteredPkgs.first)) { @@ -952,10 +955,12 @@ public final class BroadcastHelper { final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; final SparseArray<int[]> broadcastAllowList = isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds); + final String[] sharedUserPackages = + snapshot.getSharedUserPackagesForPackage(packageName, userId); mHandler.post(() -> sendPackageChangedBroadcastInternal( packageName, dontKillApp, componentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList, setting.getPkg(), - snapshot.getSharedUserPackagesForPackage(packageName, userId))); + sharedUserPackages)); mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler); } @@ -1120,7 +1125,7 @@ public final class BroadcastHelper { * @param uidList The uids of packages which have suspension changes. * @param userId The user where packages reside. */ - void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Computer snapshot, + void sendPackagesSuspendedOrUnsuspendedForUser(@NonNull Supplier<Computer> snapshotSupplier, @NonNull String intent, @NonNull String[] pkgList, @NonNull int[] uidList, @@ -1138,7 +1143,7 @@ public final class BroadcastHelper { .toBundle(); BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver = (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( - snapshot, callingUid, intentExtras); + snapshotSupplier, callingUid, intentExtras); mHandler.post(() -> sendPackageBroadcast(intent, null /* pkg */, extras, flags, null /* targetPkg */, null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, @@ -1148,7 +1153,7 @@ public final class BroadcastHelper { null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver); } - void sendMyPackageSuspendedOrUnsuspended(@NonNull Computer snapshot, + void sendMyPackageSuspendedOrUnsuspended(@NonNull Supplier<Computer> snapshotSupplier, @NonNull String[] affectedPackages, boolean suspended, int userId) { @@ -1163,6 +1168,7 @@ public final class BroadcastHelper { return; } final int[] targetUserIds = new int[] {userId}; + final Computer snapshot = snapshotSupplier.get(); for (String packageName : affectedPackages) { final Bundle appExtras = suspended ? SuspendPackageHelper.getSuspendedPackageAppExtras( @@ -1192,7 +1198,7 @@ public final class BroadcastHelper { * @param uidList The uids of packages which have suspension changes. * @param userId The user where packages reside. */ - void sendDistractingPackagesChanged(@NonNull Computer snapshot, + void sendDistractingPackagesChanged(@NonNull Supplier<Computer> snapshotSupplier, @NonNull String[] pkgList, @NonNull int[] uidList, int userId, @@ -1208,11 +1214,11 @@ public final class BroadcastHelper { null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, (callingUid, intentExtras) -> filterExtrasChangedPackageList( - snapshot, callingUid, intentExtras), + snapshotSupplier, callingUid, intentExtras), null /* bOptions */, null /* requiredPermissions */)); } - void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot, + void sendResourcesChangedBroadcastAndNotify(@NonNull Supplier<Computer> snapshotSupplier, boolean mediaStatus, boolean replacing, @NonNull ArrayList<AndroidPackage> packages) { @@ -1224,7 +1230,7 @@ public final class BroadcastHelper { packageNames[i] = pkg.getPackageName(); packageUids[i] = pkg.getUid(); } - sendResourcesChangedBroadcast(snapshot, mediaStatus, + sendResourcesChangedBroadcast(snapshotSupplier, mediaStatus, replacing, packageNames, packageUids); notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids); } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 58b1e496f5f1..be2f58dc276c 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -138,7 +138,8 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlSerializer; -import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerInternal; +import com.android.server.LocalManagerRegistry; +import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerLocal; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.pm.parsing.PackageInfoUtils; @@ -5851,10 +5852,10 @@ public class ComputerEngine implements Computer { if (isHotword) { return true; } - OnDeviceIntelligenceManagerInternal onDeviceIntelligenceManagerInternal = - mInjector.getLocalService(OnDeviceIntelligenceManagerInternal.class); - return onDeviceIntelligenceManagerInternal != null - && uid == onDeviceIntelligenceManagerInternal.getInferenceServiceUid(); + OnDeviceIntelligenceManagerLocal onDeviceIntelligenceManagerLocal = + LocalManagerRegistry.getManager(OnDeviceIntelligenceManagerLocal.class); + return onDeviceIntelligenceManagerLocal != null + && uid == onDeviceIntelligenceManagerLocal.getInferenceServiceUid(); } @Nullable diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java index c5ec73b4e2b8..c4e981d487bc 100644 --- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java +++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java @@ -123,7 +123,7 @@ public final class DistractingPackageHelper { if (!changedPackagesList.isEmpty()) { final String[] changedPackages = changedPackagesList.toArray( new String[changedPackagesList.size()]); - mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(), + mBroadcastHelper.sendDistractingPackagesChanged(mPm::snapshotComputer, changedPackages, changedUids.toArray(), userId, restrictionFlags); mPm.scheduleWritePackageRestrictions(userId); } @@ -198,7 +198,7 @@ public final class DistractingPackageHelper { if (!changedPackages.isEmpty()) { final String[] packageArray = changedPackages.toArray( new String[changedPackages.size()]); - mBroadcastHelper.sendDistractingPackagesChanged(mPm.snapshotComputer(), + mBroadcastHelper.sendDistractingPackagesChanged(mPm::snapshotComputer, packageArray, changedUids.toArray(), userId, RESTRICTION_NONE); mPm.scheduleWritePackageRestrictions(userId); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 69c6ce8ea0cd..183ceaecb70e 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3084,7 +3084,7 @@ final class InstallPackageHelper { mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath()); } - mBroadcastHelper.sendPostInstallBroadcasts(mPm.snapshotComputer(), request, packageName, + mBroadcastHelper.sendPostInstallBroadcasts(mPm::snapshotComputer, request, packageName, mPm.mRequiredPermissionControllerPackage, mPm.mRequiredVerifierPackages, mPm.mRequiredInstallerPackage, /* packageSender= */ mPm, launchedForRestore, killApp, update, archived); diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java index 4b82de048a4a..ef49f49cf040 100644 --- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java +++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java @@ -34,6 +34,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -196,21 +197,13 @@ public class PackageMonitorCallbackHelper { private void doNotifyCallbacksByIntent(Intent intent, int userId, int[] broadcastAllowList, Handler handler) { - RemoteCallbackList<IRemoteCallback> callbacks; - synchronized (mLock) { - callbacks = mCallbacks; - } - doNotifyCallbacks(callbacks, intent, userId, broadcastAllowList, handler, + doNotifyCallbacks(intent, userId, broadcastAllowList, handler, null /* filterExtrasFunction */); } private void doNotifyCallbacksByAction(String action, String pkg, Bundle extras, int[] userIds, SparseArray<int[]> broadcastAllowList, Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) { - RemoteCallbackList<IRemoteCallback> callbacks; - synchronized (mLock) { - callbacks = mCallbacks; - } for (int userId : userIds) { final Intent intent = new Intent(action, pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null); @@ -226,48 +219,58 @@ public class PackageMonitorCallbackHelper { final int[] allowUids = broadcastAllowList != null ? broadcastAllowList.get(userId) : null; - doNotifyCallbacks(callbacks, intent, userId, allowUids, handler, filterExtrasFunction); + doNotifyCallbacks(intent, userId, allowUids, handler, filterExtrasFunction); } } - private void doNotifyCallbacks(RemoteCallbackList<IRemoteCallback> callbacks, - Intent intent, int userId, int[] allowUids, Handler handler, + private void doNotifyCallbacks(Intent intent, int userId, int[] allowUids, Handler handler, BiFunction<Integer, Bundle, Bundle> filterExtrasFunction) { - handler.post(() -> callbacks.broadcast((callback, user) -> { - RegisterUser registerUser = (RegisterUser) user; - if ((registerUser.getUserId() != UserHandle.USER_ALL) && (registerUser.getUserId() - != userId)) { - return; - } - int registerUid = registerUser.getUid(); - if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID) - && !ArrayUtils.contains(allowUids, registerUid)) { - if (DEBUG) { - Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction() - + ", uid " + registerUid); - } - return; - } - Intent newIntent = intent; - if (filterExtrasFunction != null) { - final Bundle extras = intent.getExtras(); - if (extras != null) { - final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras); - if (filteredExtras == null) { - // caller is unable to access this intent + handler.post(() -> { + final ArrayList<Pair<IRemoteCallback, Intent>> target = new ArrayList<>(); + synchronized (mLock) { + mCallbacks.broadcast((callback, user) -> { + RegisterUser registerUser = (RegisterUser) user; + if ((registerUser.getUserId() != UserHandle.USER_ALL) + && (registerUser.getUserId() != userId)) { + return; + } + int registerUid = registerUser.getUid(); + if (allowUids != null && !UserHandle.isSameApp(registerUid, Process.SYSTEM_UID) + && !ArrayUtils.contains(allowUids, registerUid)) { if (DEBUG) { - Slog.w(TAG, - "Skip invoke PackageMonitorCallback for " + intent.getAction() - + " because null filteredExtras"); + Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + + intent.getAction() + ", uid " + registerUid); } return; } - newIntent = new Intent(newIntent); - newIntent.replaceExtras(filteredExtras); - } + Intent newIntent = intent; + if (filterExtrasFunction != null) { + final Bundle extras = intent.getExtras(); + if (extras != null) { + final Bundle filteredExtras = + filterExtrasFunction.apply(registerUid, extras); + if (filteredExtras == null) { + // caller is unable to access this intent + if (DEBUG) { + Slog.w(TAG, + "Skip invoke PackageMonitorCallback for " + + intent.getAction() + + " because null filteredExtras"); + } + return; + } + newIntent = new Intent(newIntent); + newIntent.replaceExtras(filteredExtras); + } + } + target.add(new Pair<>(callback, newIntent)); + }); + } + for (int i = 0; i < target.size(); i++) { + Pair<IRemoteCallback, Intent> p = target.get(i); + invokeCallback(p.first, p.second); } - invokeCallback(callback, newIntent); - })); + }); } private void invokeCallback(IRemoteCallback callback, Intent intent) { diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java index 854e142c7c2f..ec91da90729b 100644 --- a/services/core/java/com/android/server/pm/SaferIntentUtils.java +++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java @@ -213,6 +213,7 @@ public class SaferIntentUtils { * CTS tests. The code in this method shall properly avoid control flows using these arguments. */ public static void blockNullAction(IntentArgs args, List componentList) { + if (args.intent.getAction() != null) return; if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return; final Computer computer = (Computer) args.snapshot; @@ -235,14 +236,11 @@ public class SaferIntentUtils { } final ParsedMainComponent comp = infoToComponent( resolveInfo.getComponentInfo(), resolver, args.isReceiver); - if (comp != null && !comp.getIntents().isEmpty() - && args.intent.getAction() == null) { + if (comp != null && !comp.getIntents().isEmpty()) { match = false; } } else if (c instanceof IntentFilter) { - if (args.intent.getAction() == null) { - match = false; - } + match = false; } if (!match) { diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 951986fbd71a..a09d4776d986 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -227,7 +227,7 @@ public final class StorageEventHelper extends StorageEventListener { } if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded); - mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(), + mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm::snapshotComputer, true /* mediaStatus */, false /* replacing */, loaded); synchronized (mLoadedVolumes) { mLoadedVolumes.add(vol.getId()); @@ -279,7 +279,7 @@ public final class StorageEventHelper extends StorageEventListener { } if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded); - mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm.snapshotComputer(), + mBroadcastHelper.sendResourcesChangedBroadcastAndNotify(mPm::snapshotComputer, false /* mediaStatus */, false /* replacing */, unloaded); synchronized (mLoadedVolumes) { mLoadedVolumes.remove(vol.getId()); diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 4e70cc52ef90..88fd1aa159d3 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -192,21 +192,20 @@ public final class SuspendPackageHelper { } }); - final Computer newSnapshot = mPm.snapshotComputer(); if (!notifyPackagesList.isEmpty()) { final String[] changedPackages = notifyPackagesList.toArray(new String[0]); - mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer, suspended ? Intent.ACTION_PACKAGES_SUSPENDED : Intent.ACTION_PACKAGES_UNSUSPENDED, changedPackages, notifyUids.toArray(), quarantined, targetUserId); - mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages, - suspended, targetUserId); + mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(mPm::snapshotComputer, + changedPackages, suspended, targetUserId); mPm.scheduleWritePackageRestrictions(targetUserId); } // Send the suspension changed broadcast to ensure suspension state is not stale. if (!changedPackagesList.isEmpty()) { - mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined, targetUserId); @@ -343,13 +342,12 @@ public final class SuspendPackageHelper { }); mPm.scheduleWritePackageRestrictions(targetUserId); - final Computer newSnapshot = mPm.snapshotComputer(); if (!unsuspendedPackages.isEmpty()) { final String[] packageArray = unsuspendedPackages.toArray( new String[unsuspendedPackages.size()]); - mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray, - false, targetUserId); - mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot, + mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(mPm::snapshotComputer, + packageArray, false, targetUserId); + mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(mPm::snapshotComputer, Intent.ACTION_PACKAGES_UNSUSPENDED, packageArray, unsuspendedUids.toArray(), false, targetUserId); } diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 94b49e538621..1fda4782fc86 100644 --- a/services/core/java/com/android/server/pm/TEST_MAPPING +++ b/services/core/java/com/android/server/pm/TEST_MAPPING @@ -165,6 +165,22 @@ ] }, { + "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases", + "file_patterns": [ + "core/java/.*Install.*", + "services/core/.*Install.*", + "services/core/java/com/android/server/pm/.*" + ], + "options":[ + { + "exclude-annotation":"androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation":"org.junit.Ignore" + } + ] + }, + { "name": "CtsPackageInstallerCUJUpdateSelfTestCases", "file_patterns": [ "core/java/.*Install.*", diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 041f2d3a459d..04ce4e692fef 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -68,6 +68,10 @@ class UserDataPreparer { void prepareUserData(UserInfo userInfo, int flags) { try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) { final StorageManager storage = mContext.getSystemService(StorageManager.class); + if (storage == null) { + Log.e(TAG, "prepareUserData failed due to unable get StorageManager"); + return; + } /* * Internal storage must be prepared before adoptable storage, since the user's volume * keys are stored in their internal storage. @@ -159,14 +163,16 @@ class UserDataPreparer { void destroyUserData(int userId, int flags) { try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) { final StorageManager storage = mContext.getSystemService(StorageManager.class); - /* - * Volume destruction order isn't really important, but to avoid any weird issues we - * process internal storage last, the opposite of prepareUserData. - */ - for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { - final String volumeUuid = vol.getFsUuid(); - if (volumeUuid != null) { - destroyUserDataLI(volumeUuid, userId, flags); + if (storage != null) { + /* + * Volume destruction order isn't really important, but to avoid any weird issues we + * process internal storage last, the opposite of prepareUserData. + */ + for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { + final String volumeUuid = vol.getFsUuid(); + if (volumeUuid != null) { + destroyUserDataLI(volumeUuid, userId, flags); + } } } destroyUserDataLI(null /* internal storage */, userId, flags); @@ -194,9 +200,10 @@ class UserDataPreparer { } } - // All the user's data directories should be empty now, so finish the job. - storage.destroyUserStorage(volumeUuid, userId, flags); - + if (storage != null) { + // All the user's data directories should be empty now, so finish the job. + storage.destroyUserStorage(volumeUuid, userId, flags); + } } catch (Exception e) { logCriticalInfo(Log.WARN, "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e); diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java index e989d6875d15..e9cb279439a6 100644 --- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java +++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java @@ -40,7 +40,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.DodecFunction; import com.android.internal.util.function.HexConsumer; import com.android.internal.util.function.HexFunction; -import com.android.internal.util.function.NonaFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.TriFunction; import com.android.internal.util.function.UndecFunction; @@ -351,22 +351,22 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe @Override public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, @Nullable String featureId, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, int notedCount, - @NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String, - Boolean, Integer, SyncNotedAppOp> superImpl) { + @Nullable String message, boolean shouldCollectMessage, + @NonNull OctFunction<Integer, Integer, String, String, Integer, Boolean, String, + Boolean, SyncNotedAppOp> superImpl) { if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) { final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID); final long identity = Binder.clearCallingIdentity(); try { return superImpl.apply(code, shellUid, SHELL_PKG, featureId, virtualDeviceId, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount); + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } finally { Binder.restoreCallingIdentity(identity); } } return superImpl.apply(code, uid, packageName, featureId, virtualDeviceId, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount); + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } @Override diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 33210e28281e..ecffd382f542 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -53,7 +53,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.DodecFunction; import com.android.internal.util.function.HexConsumer; import com.android.internal.util.function.HexFunction; -import com.android.internal.util.function.NonaFunction; +import com.android.internal.util.function.OctFunction; import com.android.internal.util.function.QuadFunction; import com.android.internal.util.function.UndecFunction; import com.android.server.LocalServices; @@ -246,12 +246,11 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat public SyncNotedAppOp noteOperation(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId, boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage, int notedCount, - @NonNull NonaFunction<Integer, Integer, String, String, - Integer, Boolean, String, Boolean, Integer, SyncNotedAppOp> superImpl) { + boolean shouldCollectMessage, @NonNull OctFunction<Integer, Integer, String, String, + Integer, Boolean, String, Boolean, SyncNotedAppOp> superImpl) { return superImpl.apply(resolveDatasourceOp(code, uid, packageName, attributionTag), resolveUid(code, uid), packageName, attributionTag, virtualDeviceId, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, notedCount); + shouldCollectAsyncNotedOp, message, shouldCollectMessage); } @Override diff --git a/services/core/java/com/android/server/policy/EventLogTags.logtags b/services/core/java/com/android/server/policy/EventLogTags.logtags index 75633820d01f..a4b6472fbe62 100644 --- a/services/core/java/com/android/server/policy/EventLogTags.logtags +++ b/services/core/java/com/android/server/policy/EventLogTags.logtags @@ -1,4 +1,4 @@ -# See system/core/logcat/event.logtags for a description of the format of this file. +# See system/logging/logcat/event.logtags for a description of the format of this file. option java_package com.android.server.policy diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 7c4d4253157e..f1a481155458 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4081,6 +4081,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS: case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH: case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: case KeyGestureEvent.KEY_GESTURE_TYPE_HOME: case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS: case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN: @@ -4164,6 +4165,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } return true; case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT: + case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT: if (complete) { launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD, deviceId, SystemClock.uptimeMillis(), diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 8c588b4c9b98..63e8d9973237 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -195,22 +195,23 @@ public class BatteryUsageStatsProvider { mLastAccumulationMonotonicHistorySize = historySize; } - // No need to store the accumulated stats asynchronously, as the entire accumulation - // operation is async - handler.post(() -> accumulateBatteryUsageStats(stats, false)); + handler.post(() -> accumulateBatteryUsageStats(stats)); } /** * Computes BatteryUsageStats for the period since the last accumulated stats were stored, - * adds them to the accumulated stats and asynchronously saves the result. + * adds them to the accumulated stats and saves the result. */ public void accumulateBatteryUsageStats(BatteryStatsImpl stats) { - accumulateBatteryUsageStats(stats, true); - } - - private void accumulateBatteryUsageStats(BatteryStatsImpl stats, boolean storeAsync) { AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats(); - updateAccumulatedBatteryUsageStats(accumulatedStats, stats); + + final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() + .setMaxStatsAgeMs(0) + .includeProcessStateData() + .includePowerStateData() + .includeScreenStateData() + .build(); + updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query); PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID); powerStatsSpan.addSection( @@ -219,13 +220,8 @@ public class BatteryUsageStatsProvider { accumulatedStats.startWallClockTime, accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime); mMonotonicClock.write(); - if (storeAsync) { - mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan, - accumulatedStats.builder::discard); - } else { - mPowerStatsStore.storePowerStatsSpan(powerStatsSpan); - accumulatedStats.builder.discard(); - } + mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan, + accumulatedStats.builder::discard); } /** @@ -273,7 +269,7 @@ public class BatteryUsageStatsProvider { BatteryUsageStats batteryUsageStats; if ((query.getFlags() & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) { - batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query); + batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs); } else if (query.getAggregatedToTimestamp() == 0) { BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query, query.getMonotonicStartTime(), @@ -292,13 +288,9 @@ public class BatteryUsageStatsProvider { } private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats, - BatteryUsageStatsQuery query) { + BatteryUsageStatsQuery query, long currentTimeMs) { AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats(); - if (accumulatedStats.endMonotonicTime == MonotonicClock.UNDEFINED - || mMonotonicClock.monotonicTime() - accumulatedStats.endMonotonicTime - > query.getMaxStatsAge()) { - updateAccumulatedBatteryUsageStats(accumulatedStats, stats); - } + updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query); return accumulatedStats.builder.build(); } @@ -329,7 +321,7 @@ public class BatteryUsageStatsProvider { } private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats, - BatteryStatsImpl stats) { + BatteryStatsImpl stats, BatteryUsageStatsQuery query) { long startMonotonicTime = accumulatedStats.endMonotonicTime; if (startMonotonicTime == MonotonicClock.UNDEFINED) { startMonotonicTime = stats.getMonotonicStartTime(); @@ -341,7 +333,6 @@ public class BatteryUsageStatsProvider { accumulatedStats.builder = new BatteryUsageStats.Builder( stats.getCustomEnergyConsumerNames(), true, true, true, 0); accumulatedStats.startWallClockTime = stats.getStartClockTime(); - accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime(); accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime); } @@ -351,7 +342,7 @@ public class BatteryUsageStatsProvider { accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime); mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(), - startMonotonicTime, endMonotonicTime); + startMonotonicTime, MonotonicClock.UNDEFINED); populateGeneralInfo(accumulatedStats.builder, stats); } diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java index 2609cf7aebfc..dcdd3bd8b3fa 100644 --- a/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsAggregator.java @@ -23,7 +23,6 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BatteryStatsHistory; import com.android.internal.os.BatteryStatsHistoryIterator; -import com.android.internal.os.MonotonicClock; import java.util.function.Consumer; @@ -170,9 +169,6 @@ public class PowerStatsAggregator { } } } - if (endTimeMs != MonotonicClock.UNDEFINED) { - lastTime = endTimeMs; - } if (lastTime > baseTime) { mStats.setDuration(lastTime - baseTime); mStats.finish(lastTime); diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java index 0ea88e8523f0..687442b47fb3 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java +++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java @@ -28,6 +28,7 @@ import com.android.server.ServiceThread; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; public class DataAggregator { private static final String TAG = "IntrusionDetection DataAggregator"; @@ -36,11 +37,10 @@ public class DataAggregator { private static final int MSG_DISABLE = 2; private static final int STORED_EVENTS_SIZE_LIMIT = 1024; - private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER = - new IntrusionDetectionAdminReceiver(); private final IntrusionDetectionService mIntrusionDetectionService; private final ArrayList<DataSource> mDataSources; + private final AtomicBoolean mIsLoggingInitialized = new AtomicBoolean(false); private Context mContext; private List<IntrusionDetectionEvent> mStoredEvents = new ArrayList<>(); @@ -59,30 +59,20 @@ public class DataAggregator { mHandler = new EventHandler(looper, this); } - /** - * Initialize DataSources - * @return Whether the initialization succeeds. - */ - public boolean initialize() { - SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this); - mDataSources.add(securityLogSource); - - NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this); - ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource); - mDataSources.add(networkLogSource); - - for (DataSource ds : mDataSources) { - if (!ds.initialize()) { - return false; - } - } - return true; + /** Initialize DataSources */ + private void initialize() { + mDataSources.add(new SecurityLogSource(mContext, this)); + mDataSources.add(new NetworkLogSource(mContext, this)); } /** * Enable the data collection of all DataSources. */ public void enable() { + if (!mIsLoggingInitialized.get()) { + initialize(); + mIsLoggingInitialized.set(true); + } mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND, /* allowIo */ false); mHandlerThread.start(); @@ -111,9 +101,6 @@ public class DataAggregator { */ public void disable() { mHandler.obtainMessage(MSG_DISABLE).sendToTarget(); - for (DataSource ds : mDataSources) { - ds.disable(); - } } private void onNewSingleData(IntrusionDetectionEvent event) { diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java index 61fac46be82d..0bc448245b76 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java +++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java @@ -18,11 +18,6 @@ package com.android.server.security.intrusiondetection; public interface DataSource { /** - * Initialize the data source. - */ - boolean initialize(); - - /** * Enable the data collection. */ void enable(); diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java deleted file mode 100644 index dba7374fe02a..000000000000 --- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.security.intrusiondetection; - -import android.app.admin.DeviceAdminReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Slog; - -public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver { - private static final String TAG = "IntrusionDetectionAdminReceiver"; - - private static NetworkLogSource sNetworkLogSource; - - @Override - public void onNetworkLogsAvailable( - Context context, Intent intent, long batchToken, int networkLogsCount) { - if (sNetworkLogSource != null) { - sNetworkLogSource.onNetworkLogsAvailable(batchToken); - } else { - Slog.w(TAG, "Network log receiver is not initialized"); - } - } - - public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) { - sNetworkLogSource = networkLogSource; - } -} diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java index 2ddbeab5c8d8..a16e66de09a9 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java +++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java @@ -50,7 +50,7 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec private static final String PRODUCTION_BUILD = "user"; private static final String PROPERTY_BUILD_TYPE = "ro.build.type"; private static final String PROPERTY_INTRUSION_DETECTION_SERVICE_NAME = - "intrusiondetection_service_name"; + "debug.intrusiondetection_package_name"; private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min private static final String TAG = "IntrusionDetectionEventTransportConnection"; private final Context mContext; @@ -147,9 +147,9 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec } private String getSystemPropertyValue(String propertyName) { - String comamandString = "getprop " + propertyName; + String commandString = "getprop " + propertyName; try { - Process process = Runtime.getRuntime().exec(comamandString); + Process process = Runtime.getRuntime().exec(commandString); BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String propertyValue = reader.readLine(); @@ -175,6 +175,10 @@ public class IntrusionDetectionEventTransportConnection implements ServiceConnec mIntrusionDetectionEventTransportConfig = getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME); } + Slog.d( + TAG, + "mIntrusionDetectionEventTransportConfig: " + + mIntrusionDetectionEventTransportConfig); if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) { Slog.e(TAG, "Unable to find a valid config for the transport service"); diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java index 0287b415b9c2..8ff1c7f22ffb 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java +++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionService.java @@ -232,12 +232,10 @@ public class IntrusionDetectionService extends SystemService { return; } - // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest. - // Enable it when the transport component is ready. - // if (!mIntrusionDetectionEventTransportConnection.initialize()) { - // callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE); - // return; - // } + if (!mIntrusionDetectionEventTransportConnection.initialize()) { + callback.onFailure(ERROR_TRANSPORT_UNAVAILABLE); + return; + } mDataAggregator.enable(); mState = STATE_ENABLED; @@ -252,9 +250,7 @@ public class IntrusionDetectionService extends SystemService { return; } - // TODO: temporarily disable the following for the CTS IntrusionDetectionManagerTest. - // Enable it when the transport component is ready. - // mIntrusionDetectionEventTransportConnection.release(); + mIntrusionDetectionEventTransportConnection.release(); mDataAggregator.disable(); mState = STATE_DISABLED; notifyStateMonitors(); diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java index 1c93d3f9c6a1..083b1fd61c46 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java +++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java @@ -17,118 +17,131 @@ package com.android.server.security.intrusiondetection; import android.app.admin.ConnectEvent; -import android.app.admin.DevicePolicyManager; import android.app.admin.DnsEvent; -import android.app.admin.NetworkEvent; -import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.net.IIpConnectivityMetrics; +import android.net.INetdEventCallback; +import android.net.metrics.IpConnectivityLog; +import android.os.RemoteException; +import android.os.ServiceManager; import android.security.intrusiondetection.IntrusionDetectionEvent; import android.util.Slog; -import java.util.List; -import java.util.stream.Collectors; +import com.android.server.LocalServices; +import com.android.server.net.BaseNetdEventCallback; + +import java.util.concurrent.atomic.AtomicBoolean; public class NetworkLogSource implements DataSource { private static final String TAG = "IntrusionDetectionEvent NetworkLogSource"; + private final AtomicBoolean mIsNetworkLoggingEnabled = new AtomicBoolean(false); + private final PackageManagerInternal mPm; - private DevicePolicyManager mDpm; - private ComponentName mAdmin; private DataAggregator mDataAggregator; - public NetworkLogSource(Context context, DataAggregator dataAggregator) { + private IIpConnectivityMetrics mIpConnectivityMetrics; + private long mId; + + public NetworkLogSource(Context context, DataAggregator dataAggregator) + throws SecurityException { mDataAggregator = dataAggregator; - mDpm = context.getSystemService(DevicePolicyManager.class); - mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class); + mPm = LocalServices.getService(PackageManagerInternal.class); + mId = 0; + initIpConnectivityMetrics(); } - @Override - public boolean initialize() { - try { - if (!mDpm.isAdminActive(mAdmin)) { - Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin"); - return false; - } - } catch (SecurityException e) { - Slog.e(TAG, "Security exception in initialize: ", e); - return false; - } - return true; + private void initIpConnectivityMetrics() { + mIpConnectivityMetrics = + (IIpConnectivityMetrics) + IIpConnectivityMetrics.Stub.asInterface( + ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); } @Override public void enable() { - enableNetworkLog(); - } - - @Override - public void disable() { - disableNetworkLog(); - } - - private void enableNetworkLog() { - if (!isNetworkLogEnabled()) { - mDpm.setNetworkLoggingEnabled(mAdmin, true); + if (mIsNetworkLoggingEnabled.get()) { + Slog.w(TAG, "Network logging is already enabled"); + return; } - } - - private void disableNetworkLog() { - if (isNetworkLogEnabled()) { - mDpm.setNetworkLoggingEnabled(mAdmin, false); + try { + if (mIpConnectivityMetrics.addNetdEventCallback( + INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) { + mIsNetworkLoggingEnabled.set(true); + } else { + Slog.e(TAG, "Failed to enable network logging; invalid callback"); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to enable network logging; ", e); } } - private boolean isNetworkLogEnabled() { - return mDpm.isNetworkLoggingEnabled(mAdmin); - } - - /** - * Retrieve network logs when onNetworkLogsAvailable callback is received. - * - * @param batchToken The token representing the current batch of network logs. - */ - public void onNetworkLogsAvailable(long batchToken) { - List<NetworkEvent> events; - try { - events = mDpm.retrieveNetworkLogs(mAdmin, batchToken); - } catch (SecurityException e) { - Slog.e( - TAG, - "Admin " - + mAdmin.flattenToString() - + "does not have permission to retrieve network logs", - e); + @Override + public void disable() { + if (!mIsNetworkLoggingEnabled.get()) { + Slog.w(TAG, "Network logging is already disabled"); return; } - if (events == null) { - if (!isNetworkLogEnabled()) { - Slog.w(TAG, "Network logging is disabled"); + try { + if (!mIpConnectivityMetrics.removeNetdEventCallback( + INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST)) { + + mIsNetworkLoggingEnabled.set(false); } else { - Slog.e(TAG, "Invalid batch token: " + batchToken); + Slog.e(TAG, "Failed to enable network logging; invalid callback"); } - return; + } catch (RemoteException e) { + Slog.e(TAG, "Failed to disable network logging; ", e); } - - List<IntrusionDetectionEvent> intrusionDetectionEvents = - events.stream() - .filter(event -> event != null) - .map(event -> toIntrusionDetectionEvent(event)) - .collect(Collectors.toList()); - mDataAggregator.addBatchData(intrusionDetectionEvents); } - private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) { - if (event instanceof DnsEvent) { - DnsEvent dnsEvent = (DnsEvent) event; - return new IntrusionDetectionEvent(dnsEvent); - } else if (event instanceof ConnectEvent) { - ConnectEvent connectEvent = (ConnectEvent) event; - return new IntrusionDetectionEvent(connectEvent); + private void incrementEventID() { + if (mId == Long.MAX_VALUE) { + Slog.i(TAG, "Reached maximum id value; wrapping around."); + mId = 0; + } else { + mId++; } - throw new IllegalArgumentException( - "Invalid event type with ID: " - + event.getId() - + "from package: " - + event.getPackageName()); } + + private final INetdEventCallback mNetdEventCallback = + new BaseNetdEventCallback() { + @Override + public void onDnsEvent( + int netId, + int eventType, + int returnCode, + String hostname, + String[] ipAddresses, + int ipAddressesCount, + long timestamp, + int uid) { + if (!mIsNetworkLoggingEnabled.get()) { + return; + } + DnsEvent dnsEvent = + new DnsEvent( + hostname, + ipAddresses, + ipAddressesCount, + mPm.getNameForUid(uid), + timestamp); + dnsEvent.setId(mId); + incrementEventID(); + mDataAggregator.addSingleData(new IntrusionDetectionEvent(dnsEvent)); + } + + @Override + public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) { + if (!mIsNetworkLoggingEnabled.get()) { + return; + } + ConnectEvent connectEvent = + new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), timestamp); + connectEvent.setId(mId); + incrementEventID(); + mDataAggregator.addSingleData(new IntrusionDetectionEvent(connectEvent)); + } + }; } diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java index c5f736e383b2..5611905bf270 100644 --- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java +++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java @@ -43,26 +43,9 @@ public class SecurityLogSource implements DataSource { mDataAggregator = dataAggregator; mDpm = context.getSystemService(DevicePolicyManager.class); mExecutor = Executors.newSingleThreadExecutor(); - } - - @Override - public boolean initialize() { - // Confirm caller is system and the device is managed. Otherwise logs will - // be redacted. - try { - if (!mDpm.isDeviceManaged()) { - Slog.e(TAG, "Caller does not have device owner permissions"); - return false; - } - } catch (SecurityException e) { - Slog.e(TAG, "Security exception in initialize: ", e); - return false; - } mEventCallback = new SecurityEventCallback(); - return true; } - @Override @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void enable() { @@ -99,6 +82,10 @@ public class SecurityLogSource implements DataSource { @Override public void accept(List<SecurityEvent> events) { + if (events.size() == 0) { + Slog.w(TAG, "No events received; caller may not be authorized"); + return; + } List<IntrusionDetectionEvent> intrusionDetectionEvents = events.stream() .filter(event -> event != null) diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java index 63a3e5ae7d8b..a38fc5bb6e45 100644 --- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java +++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.PackageManagerInternal; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; @@ -63,7 +64,10 @@ public class TelecomLoaderService extends SystemService { // as this loader (process="system") that's redundant here. try { ITelecomLoader telecomLoader = ITelecomLoader.Stub.asInterface(service); - ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo); + PackageManagerInternal packageManagerInternal = + LocalServices.getService(PackageManagerInternal.class); + ITelecomService telecomService = telecomLoader.createTelecomService(mServiceRepo, + packageManagerInternal.getSystemUiServiceComponent().getPackageName()); SmsApplication.getDefaultMmsApplication(mContext, false); ServiceManager.addService(Context.TELECOM_SERVICE, telecomService.asBinder()); diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 24ed1bbe0eb1..57a0bb53c9dc 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static com.android.server.wm.SnapshotPersistQueue.MAX_STORE_QUEUE_DEPTH; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -343,6 +345,11 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord if (DEBUG) { Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity); } + if (mPersister.mSnapshotPersistQueue.peekWriteQueueSize() >= MAX_STORE_QUEUE_DEPTH + || mPersister.mSnapshotPersistQueue.peekQueueSize() > MAX_PERSIST_SNAPSHOT_COUNT) { + Slog.w(TAG, "Skipping recording activity snapshot, too many requests!"); + return; + } final int size = activity.size(); final int[] mixedCode = new int[size]; if (size == 1) { @@ -432,7 +439,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord addBelowActivityIfExist(ar, mPendingLoadActivity, false, "load-snapshot"); } else { // remove the snapshot for the one below close - addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot"); + addBelowActivityIfExist(ar, mPendingRemoveActivity, false, "remove-snapshot"); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 90d3834b4543..2781592c6b4f 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -27,6 +27,7 @@ import static android.app.ActivityManager.START_RETURN_INTENT_TO_CALLER; import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; +import static android.app.ActivityManager.isStartResultSuccessful; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -891,7 +892,10 @@ class ActivityStarter { final ActivityOptions originalOptions = mRequest.activityOptions != null ? mRequest.activityOptions.getOriginalOptions() : null; // Only track the launch time of activity that will be resumed. - launchingRecord = mDoResume ? mLastStartActivityRecord : null; + if (mDoResume || (isStartResultSuccessful(res) + && mLastStartActivityRecord.getTask().isVisibleRequested())) { + launchingRecord = mLastStartActivityRecord; + } // If the new record is the one that started, a new activity has created. final boolean newActivityCreated = mStartActivity == launchingRecord; // Notify ActivityMetricsLogger that the activity has launched. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0ebdaed1bc02..afa7ea136c77 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1555,7 +1555,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityRecord[] outActivity = new ActivityRecord[1]; - getActivityStartController().obtainStarter(intent, "dream") + final int res = getActivityStartController() + .obtainStarter(intent, "dream") .setCallingUid(callingUid) .setCallingPid(callingPid) .setCallingPackage(intent.getPackage()) @@ -1569,9 +1570,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { .execute(); final ActivityRecord started = outActivity[0]; - final IAppTask appTask = started == null ? null : - new AppTaskImpl(this, started.getTask().mTaskId, callingUid); - return appTask; + if (started == null || !ActivityManager.isStartResultSuccessful(res)) { + // start the dream activity failed. + return null; + } + return new AppTaskImpl(this, started.getTask().mTaskId, callingUid); } } diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 93ccd74c6b23..6ccceb9cf564 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -243,6 +243,19 @@ final class ContentRecorder implements WindowContainerListener { } } + /** Called when the surface of display is changed to a different instance. */ + void resetRecordingDisplay(int displayId) { + if (!isCurrentlyRecording() + || mContentRecordingSession.getDisplayToRecord() != displayId) { + return; + } + ProtoLog.v(WM_DEBUG_CONTENT_RECORDING, + "Content Recording: Display %d changed surface so stop recording", displayId); + mDisplayContent.mWmService.mTransactionFactory.get().remove(mRecordedSurface).apply(); + mRecordedSurface = null; + // Do not un-set the token, in case new surface is ready and recording should begin again. + } + /** * Pauses recording on this display content. Note the session does not need to be updated, * since recording can be resumed still. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3189acbc2fbf..c2141a7103be 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -260,6 +260,7 @@ import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.RegionUtils; import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -1272,7 +1273,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override void migrateToNewSurfaceControl(Transaction t) { t.remove(mSurfaceControl); - + // Reset the recording displays which were mirroring this display. + for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) { + final ContentRecorder recorder = mRootWindowContainer.getChildAt(i).mContentRecorder; + if (recorder != null) { + recorder.resetRecordingDisplay(mDisplayId); + } + } mLastSurfacePosition.set(0, 0); mLastDeltaRotation = Surface.ROTATION_0; @@ -1384,11 +1391,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTokenMap.put(binder, token); if (token.asActivityRecord() == null) { - // Set displayContent for non-app token to prevent same token will add twice after - // onDisplayChanged. - // TODO: Check if it's fine that super.onDisplayChanged of WindowToken - // (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent assigned. - token.mDisplayContent = this; + // Setting the mDisplayContent to the token is not needed: it is done by da.addChild + // below, that also calls onDisplayChanged once moved. + if (!Flags.reparentWindowTokenApi()) { + // Set displayContent for non-app token to prevent same token will add twice after + // onDisplayChanged. + // TODO: Check if it's fine that super.onDisplayChanged of WindowToken + // (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent + // assigned. + token.mDisplayContent = this; + } // Add non-app token to container hierarchy on the display. App tokens are added through // the parent container managing them (e.g. Tasks). final DisplayArea.Tokens da = findAreaForToken(token).asTokens(); diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 2a5a3a5638d2..1c4e487d2e7e 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -528,7 +528,7 @@ class DragState { } // Only allow the extras to be dispatched to a global-intercepting drag target ClipData data = null; - if (interceptsGlobalDrag) { + if (interceptsGlobalDrag && mData != null) { data = mData.copyForTransferWithActivityInfo(); PersistableBundle extras = data.getDescription().getExtras() != null ? data.getDescription().getExtras() diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 27f82d90fdac..7fdc2c67b5ce 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -40,7 +40,7 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_TASKS; -import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; +import static com.android.launcher3.Flags.enableUseTopVisibleActivityForExcludeFromRecentTask; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; @@ -1529,7 +1529,7 @@ class RecentTasks { // The Recents is only supported on default display now, we should only keep the // most recent task of home display. boolean isMostRecentTask; - if (enableRefactorTaskThumbnail()) { + if (enableUseTopVisibleActivityForExcludeFromRecentTask()) { isMostRecentTask = task.getTopVisibleActivity() != null; } else { isMostRecentTask = taskIndex == 0; diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index 8b63ecf7135f..a5454546341b 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -50,7 +50,7 @@ import java.util.ArrayDeque; class SnapshotPersistQueue { private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM; private static final long DELAY_MS = 100; - private static final int MAX_STORE_QUEUE_DEPTH = 2; + static final int MAX_STORE_QUEUE_DEPTH = 2; private static final int COMPRESS_QUALITY = 95; @GuardedBy("mLock") @@ -154,7 +154,12 @@ class SnapshotPersistQueue { } } - @VisibleForTesting + int peekWriteQueueSize() { + synchronized (mLock) { + return mStoreQueueItems.size(); + } + } + int peekQueueSize() { synchronized (mLock) { return mWriteQueue.size(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4ed120631bd5..810aa0454246 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4489,7 +4489,7 @@ class Task extends TaskFragment { } void onPictureInPictureParamsChanged() { - if (inPinnedWindowingMode()) { + if (inPinnedWindowingMode() || Flags.enableDesktopWindowingPip()) { dispatchTaskInfoChangedIfNeeded(true /* force */); } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 8562bb23b30f..f3c03cbfb3b4 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -791,19 +791,30 @@ class TransitionController { ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Requesting StartTransition: %s", transition); ActivityManager.RunningTaskInfo startTaskInfo = null; - ActivityManager.RunningTaskInfo pipTaskInfo = null; + TransitionRequestInfo.PipChange pipChange = null; if (startTask != null) { startTaskInfo = startTask.getTaskInfo(); } // set the pip task in the request if provided if (transition.getPipActivity() != null) { - pipTaskInfo = transition.getPipActivity().getTask().getTaskInfo(); + ActivityManager.RunningTaskInfo pipTaskInfo = + transition.getPipActivity().getTask().getTaskInfo(); + ActivityRecord pipActivity = transition.getPipActivity(); + if (pipActivity.getTaskFragment() != null + && pipActivity.getTaskFragment() != pipActivity.getTask()) { + // If the PiP activity is in a TF different from its task, this could be + // AE-to-PiP case, so PipChange will have the TF token cached separately. + pipChange = new TransitionRequestInfo.PipChange(pipActivity.getTaskFragment() + .mRemoteToken.toWindowContainerToken(), pipTaskInfo); + } else { + pipChange = new TransitionRequestInfo.PipChange(pipTaskInfo); + } transition.setPipActivity(null); } final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType, - startTaskInfo, pipTaskInfo, remoteTransition, displayChange, + startTaskInfo, pipChange, remoteTransition, displayChange, transition.getFlags(), transition.getSyncId()); transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 809745e48300..6d0da1fd36f5 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -167,6 +167,22 @@ class WindowContextListenerController { return true; } + boolean assertCallerCanReparentListener(@NonNull IBinder clientToken, + boolean callerCanManageAppTokens, int callingUid, int displayId) { + if (!assertCallerCanModifyListener(clientToken, callerCanManageAppTokens, callingUid)) { + return false; + } + + final WindowContainer<?> container = getContainer(clientToken); + if (container != null && container.getDisplayContent() != null + && container.getDisplayContent().mDisplayId == displayId) { + ProtoLog.i(WM_DEBUG_ADD_REMOVE, + "The listener has already been attached to the same display id"); + return false; + } + return true; + } + boolean hasListener(IBinder clientToken) { return mListeners.containsKey(clientToken); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b42ce64fa1d2..0b6ca75c5f0d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -358,6 +358,7 @@ import com.android.server.policy.WindowManagerPolicy.ScreenOffListener; import com.android.server.power.ShutdownThread; import com.android.server.utils.PriorityDump; import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; +import com.android.window.flags.Flags; import dalvik.annotation.optimization.NeverCompile; @@ -3042,6 +3043,81 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public boolean reparentWindowContextToDisplayArea( + @NonNull IApplicationThread appThread, @NonNull IBinder clientToken, int displayId) { + if (!Flags.reparentWindowTokenApi()) { + return false; + } + Objects.requireNonNull(appThread); + Objects.requireNonNull(clientToken); + final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS, + "reparentWindowContextToDisplayArea", false /* printLog */); + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final WindowProcessController wpc = mAtmService.getProcessController(appThread); + if (wpc == null) { + ProtoLog.w(WM_ERROR, "reparentWindowContextToDisplayArea: calling from" + + " non-existing process pid=%d uid=%d", callingPid, callingUid); + return false; + } + final DisplayContent dc = mRoot.getDisplayContentOrCreate(displayId); + if (dc == null) { + ProtoLog.w(WM_ERROR, "reparentWindowContextToDisplayArea: trying to attach" + + " to a non-existing display:%d", displayId); + return false; + } + + if (!mWindowContextListenerController.assertCallerCanReparentListener(clientToken, + callerCanManageAppTokens, callingUid, displayId)) { + return false; + } + final WindowContainer<?> container = mWindowContextListenerController.getContainer( + clientToken); + + final WindowToken token = container != null ? container.asWindowToken() : null; + if (token != null && token.isFromClient()) { + ProtoLog.d(WM_DEBUG_ADD_REMOVE, "Reparenting from dc to displayId=%d", + displayId); + // Reparent the window created for this window context. + dc.reParentWindowToken(token); + hideUntilNextDraw(token); + // This makes sure there is a traversal scheduled that will eventually report + // the window resize to the client. + dc.setLayoutNeeded(); + requestTraversal(); + return true; + } + + final int type = mWindowContextListenerController.getWindowType(clientToken); + final Bundle options = mWindowContextListenerController.getOptions(clientToken); + // No window yet, switch listening DA. + final DisplayArea<?> da = dc.findAreaForWindowType(type, options, + callerCanManageAppTokens, false /* roundedCornerOverlay */); + mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken, + da, type, options, true /* shouldDispatchConfigWhenRegistering */); + return true; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + private void hideUntilNextDraw(@NonNull WindowToken token) { + final WindowState topChild = token.getTopChild(); + if (topChild != null) { + mTransactionFactory.get().hide(token.mSurfaceControl).apply(); + topChild.applyWithNextDraw(t -> { + if (token.mSurfaceControl != null) { + t.show(token.mSurfaceControl); + } + }); + } + } + /** Returns {@code true} if this binder is a registered window token. */ @Override public boolean isWindowToken(IBinder binder) { @@ -9020,16 +9096,19 @@ public class WindowManagerService extends IWindowManager.Stub clearPointerDownOutsideFocusRunnable(); + final InputTarget focusedInputTarget = mFocusedInputTarget; if (shouldDelayTouchOutside(t)) { - mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t); + mPointerDownOutsideFocusRunnable = + () -> handlePointerDownOutsideFocus(t, focusedInputTarget); mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS); } else if (!fromHandler) { // Still post the runnable to handler thread in case there is already a runnable // in execution, but still waiting to hold the wm lock. - mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t); + mPointerDownOutsideFocusRunnable = + () -> handlePointerDownOutsideFocus(t, focusedInputTarget); mH.post(mPointerDownOutsideFocusRunnable); } else { - handlePointerDownOutsideFocus(t); + handlePointerDownOutsideFocus(t, focusedInputTarget); } } @@ -9061,8 +9140,15 @@ public class WindowManagerService extends IWindowManager.Stub return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform; } - private void handlePointerDownOutsideFocus(InputTarget t) { + private void handlePointerDownOutsideFocus(InputTarget t, InputTarget focusedInputTarget) { synchronized (mGlobalLock) { + if (mFocusedInputTarget != focusedInputTarget) { + // Skip if the mFocusedInputTarget is already changed. This is possible if the + // pointer-down-outside-focus event is delayed to be handled. + ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, + "Skip onPointerDownOutsideFocusLocked due to input target changed %s", t); + return; + } if (mPointerDownOutsideFocusRunnable != null && mH.hasCallbacks(mPointerDownOutsideFocusRunnable)) { // Skip if there's another pending pointer-down-outside-focus event. diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ddff24d35232..66921ff3adeb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1335,11 +1335,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK: { final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); - Task pipTask = container.asTask(); - if (pipTask == null) { + TaskFragment pipTaskFragment = container.asTaskFragment(); + if (pipTaskFragment == null) { break; } - ActivityRecord pipActivity = pipTask.getActivity( + ActivityRecord pipActivity = pipTaskFragment.getActivity( (activity) -> activity.pictureInPictureArgs != null); if (pipActivity.isState(RESUMED)) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 832295a7e031..cca73c574951 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -49,6 +49,7 @@ import android.window.WindowContext; import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -386,7 +387,15 @@ class WindowToken extends WindowContainer<WindowState> { @Override void onDisplayChanged(DisplayContent dc) { - dc.reParentWindowToken(this); + if (!Flags.reparentWindowTokenApi()) { + dc.reParentWindowToken(this); + } else { + // This check is needed to break recursion, as DisplayContent#reparentWindowToken also + // triggers a WindowToken#onDisplayChanged. + if (dc.getWindowToken(token) == null) { + dc.reParentWindowToken(this); + } + } // TODO(b/36740756): One day this should perhaps be hooked // up with goodToGo, so we don't move a window diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 4d318f9036eb..2627895b8c63 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -9089,7 +9089,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CallerIdentity caller = getCallerIdentity(who); if (Flags.setAutoTimeEnabledCoexistence()) { - Preconditions.checkCallAuthorization(hasPermission(SET_TIME, caller.getPackageName())); + Preconditions.checkCallAuthorization(hasPermission(SET_TIME, callerPackageName)); } else { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) @@ -9152,7 +9152,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( /* who */ null, SET_TIME, - caller.getPackageName(), + callerPackageName, UserHandle.USER_ALL ); Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin( @@ -9197,7 +9197,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CallerIdentity caller = getCallerIdentity(who); if (Flags.setAutoTimeZoneEnabledCoexistence()) { Preconditions.checkCallAuthorization( - hasPermission(SET_TIME_ZONE, caller.getPackageName())); + hasPermission(SET_TIME_ZONE, callerPackageName)); } else { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) @@ -9255,7 +9255,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( /* who */ null, SET_TIME_ZONE, - caller.getPackageName(), + callerPackageName, UserHandle.USER_ALL ); Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin( diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index aa63c4a4a91f..92dbf63b3cf7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -201,7 +201,6 @@ import com.android.server.net.watchlist.NetworkWatchlistService; import com.android.server.notification.NotificationManagerService; import com.android.server.oemlock.OemLockService; import com.android.server.om.OverlayManagerService; -import com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.NativeTombstoneManagerService; @@ -392,6 +391,8 @@ public final class SystemServer implements Dumpable { "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle"; private static final String AD_SERVICES_MANAGER_SERVICE_CLASS = "com.android.server.adservices.AdServicesManagerService$Lifecycle"; + private static final String ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS = + "com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService"; private static final String ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS = "com.android.server.ondevicepersonalization." + "OnDevicePersonalizationSystemService$Lifecycle"; @@ -2932,7 +2933,7 @@ public final class SystemServer implements Dumpable { t.traceEnd(); // UprobeStats DynamicInstrumentationManager - if (com.android.art.flags.Flags.executableMethodFileOffsets()) { + if (android.uprobestats.flags.Flags.executableMethodFileOffsets()) { t.traceBegin("StartDynamicInstrumentationManager"); mSystemServiceManager.startService(DynamicInstrumentationManagerService.class); t.traceEnd(); @@ -3453,7 +3454,7 @@ public final class SystemServer implements Dumpable { private void startOnDeviceIntelligenceService(TimingsTraceAndSlog t) { t.traceBegin("startOnDeviceIntelligenceManagerService"); - mSystemServiceManager.startService(OnDeviceIntelligenceManagerService.class); + mSystemServiceManager.startService(ON_DEVICE_INTELLIGENCE_MANAGER_SERVICE_CLASS); t.traceEnd(); } diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig index 4412968999e5..0d222fb4409e 100644 --- a/services/java/com/android/server/flags.aconfig +++ b/services/java/com/android/server/flags.aconfig @@ -10,14 +10,6 @@ flag { } flag { - name: "remove_java_service_manager_cache" - namespace: "system_performance" - description: "This flag turns off Java's Service Manager caching mechanism." - bug: "333854840" - is_fixed_read_only: true -} - -flag { name: "remove_text_service" namespace: "wear_frameworks" description: "Remove TextServiceManagerService on Wear" diff --git a/services/proguard.flags b/services/proguard.flags index 977bd19a7236..0e1f68e03d7d 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -44,6 +44,9 @@ -keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; } +# allow invoking start-service using class name in both apex and services jar. +-keep,allowoptimization,allowaccessmodification class com.android.server.ondeviceintelligence.OnDeviceIntelligenceManagerService { *; } + # Keep all aconfig Flag class as they might be statically referenced by other packages # An merge or inlining could lead to missing dependencies that cause run time errors -keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; } diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java index 5492ba6b9dd1..6e14bad11837 100644 --- a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java +++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import android.os.instrumentation.MethodDescriptor; +import android.os.instrumentation.MethodDescriptorParser; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -37,7 +38,7 @@ import java.lang.reflect.Method; /** * Test class for - * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader, + * {@link MethodDescriptorParser#parseMethodDescriptor(ClassLoader, * MethodDescriptor)}. * <p> * Build/Install/Run: @@ -119,13 +120,13 @@ public class ParseMethodDescriptorTest { } private Method parseMethodDescriptor(String fqcn, String methodName) { - return DynamicInstrumentationManagerService.parseMethodDescriptor( + return MethodDescriptorParser.parseMethodDescriptor( getClass().getClassLoader(), getMethodDescriptor(fqcn, methodName, new String[]{})); } private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) { - return DynamicInstrumentationManagerService.parseMethodDescriptor( + return MethodDescriptorParser.parseMethodDescriptor( getClass().getClassLoader(), getMethodDescriptor(fqcn, methodName, fqParameters)); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt index de029e0d770f..90e1263b76ec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt @@ -56,8 +56,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java), - pkgListCaptor.capture(), any(), any(), flagsCaptor.capture()) + verify(broadcastHelper).sendDistractingPackagesChanged( + any(), pkgListCaptor.capture(), any(), any(), flagsCaptor.capture()) val modifiedPackages = pkgListCaptor.value val distractionFlags = flagsCaptor.value @@ -158,8 +158,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) verify(broadcastHelper).sendDistractingPackagesChanged( - any(Computer::class.java), pkgListCaptor.capture(), any(), eq(TEST_USER_ID), - flagsCaptor.capture()) + any(), pkgListCaptor.capture(), any(), eq(TEST_USER_ID), flagsCaptor.capture()) val modifiedPackages = pkgListCaptor.value val distractionFlags = flagsCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) @@ -198,12 +197,12 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { @Test fun sendDistractingPackagesChanged() { - broadcastHelper.sendDistractingPackagesChanged(pms.snapshotComputer(), + broadcastHelper.sendDistractingPackagesChanged(pms::snapshotComputer, packagesToChange, uidsToChange, TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS) testHandler.flush() - verify(broadcastHelper).sendDistractingPackagesChanged(any(Computer::class.java), - pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any()) + verify(broadcastHelper).sendDistractingPackagesChanged( + any(), pkgListCaptor.capture(), uidsCaptor.capture(), eq(TEST_USER_ID), any()) var changedPackages = pkgListCaptor.value var changedUids = uidsCaptor.value diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index 7444403f88c8..60e82501db1c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -58,8 +58,8 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), - eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any()) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(), + eq(Intent.ACTION_PACKAGES_SUSPENDED), pkgListCaptor.capture(), any(), any(), any()) var modifiedPackages = pkgListCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) @@ -149,11 +149,11 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), - eq(Intent.ACTION_PACKAGES_UNSUSPENDED), pkgListCaptor.capture(), any(), any(), - any()) - verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java), - any(), any(), any()) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser( + any(), eq(Intent.ACTION_PACKAGES_UNSUSPENDED), + pkgListCaptor.capture(), any(), any(), any()) + verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended( + any(), any(), any(), any()) var modifiedPackages = pkgListCaptor.value assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2) @@ -230,10 +230,10 @@ class SuspendPackageHelperTest : PackageHelperTestBase() { testHandler.flush() verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) - verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser(any(Computer::class.java), - eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any()) - verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended(any(Computer::class.java), - any(), any(), any()) + verify(broadcastHelper).sendPackagesSuspendedOrUnsuspendedForUser( + any(), eq(Intent.ACTION_PACKAGES_UNSUSPENDED), any(), any(), any(), any()) + verify(broadcastHelper).sendMyPackageSuspendedOrUnsuspended( + any(), any(), any(), any()) assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(), TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull() diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp index a31a3fb65700..f235da2fea7f 100644 --- a/services/tests/ondeviceintelligencetests/Android.bp +++ b/services/tests/ondeviceintelligencetests/Android.bp @@ -44,14 +44,18 @@ android_test { "truth", "frameworks-base-testutils", "androidx.test.rules", - ], + ] + select(soong_config_variable("ANDROID", "release_ondevice_intelligence_module"), { + "true": [ + "service-ondeviceintelligence.impl", + ], + default: [], + }), libs: [ "android.test.mock.stubs.system", "android.test.base.stubs.system", "android.test.runner.stubs.system", ], - certificate: "platform", platform_apis: true, test_suites: ["device-tests"], diff --git a/services/tests/ondeviceintelligencetests/OWNERS b/services/tests/ondeviceintelligencetests/OWNERS index 09774f78d712..a4fc7582a785 100644 --- a/services/tests/ondeviceintelligencetests/OWNERS +++ b/services/tests/ondeviceintelligencetests/OWNERS @@ -1 +1,3 @@ -file:/core/java/android/app/ondeviceintelligence/OWNERS +shiqing@google.com +sandeepbandaru@google.com +shivanker@google.com diff --git a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java index d12579cd6b11..28ccb84c38f3 100644 --- a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java +++ b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java @@ -21,7 +21,6 @@ import static com.google.common.truth.Truth.assertThat; import android.app.ondeviceintelligence.InferenceInfo; import android.os.Bundle; import android.os.PersistableBundle; -import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import android.util.Base64; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -38,6 +37,8 @@ import java.util.List; public class InferenceInfoStoreTest { InferenceInfoStore inferenceInfoStore; + public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info"; + @Before public void setUp() { inferenceInfoStore = new InferenceInfoStore(1000); @@ -46,7 +47,7 @@ public class InferenceInfoStoreTest { @Test public void testInferenceInfoParsesFromBundleSuccessfully() throws Exception { Bundle bundle = new Bundle(); - bundle.putByteArray(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + bundle.putByteArray(INFERENCE_INFO_BUNDLE_KEY, getInferenceInfoBytes(1, 1, 100)); inferenceInfoStore.addInferenceInfoFromBundle(bundle); List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0); @@ -59,7 +60,7 @@ public class InferenceInfoStoreTest { @Test public void testInferenceInfoParsesFromPersistableBundleSuccessfully() throws Exception { PersistableBundle bundle = new PersistableBundle(); - bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + bundle.putString(INFERENCE_INFO_BUNDLE_KEY, Base64.encodeToString(getInferenceInfoBytes(1, 1, 100), Base64.DEFAULT)); inferenceInfoStore.addInferenceInfoFromBundle(bundle); List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0); @@ -74,11 +75,11 @@ public class InferenceInfoStoreTest { public void testEvictionAfterMaxAge() throws Exception { PersistableBundle bundle = new PersistableBundle(); long testStartTime = System.currentTimeMillis(); - bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + bundle.putString(INFERENCE_INFO_BUNDLE_KEY, Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 10, testStartTime + 100), Base64.DEFAULT)); inferenceInfoStore.addInferenceInfoFromBundle(bundle); - bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY, + bundle.putString(INFERENCE_INFO_BUNDLE_KEY, Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 5, testStartTime + 100), Base64.DEFAULT)); inferenceInfoStore.addInferenceInfoFromBundle(bundle); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 73dcfe77e67f..709f83ba907d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -36,7 +36,6 @@ import android.os.BatteryStats; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.ConditionVariable; -import android.os.Handler; import android.os.Parcel; import android.os.Process; import android.os.UidBatteryConsumer; @@ -82,9 +81,8 @@ public class BatteryUsageStatsProviderTest { .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0); private MockClock mMockClock = mStatsRule.getMockClock(); - private MonotonicClock mMonotonicClock = mStatsRule.getMonotonicClock(); + private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock); private Context mContext; - private PowerStatsStore mPowerStatsStore; @Before public void setup() throws IOException { @@ -95,9 +93,6 @@ public class BatteryUsageStatsProviderTest { } else { mContext = InstrumentationRegistry.getContext(); } - mPowerStatsStore = spy(new PowerStatsStore( - new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()), - mStatsRule.getHandler())); } @Test @@ -279,7 +274,10 @@ public class BatteryUsageStatsProviderTest { powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, true); - BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider(0); + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, + powerAttributor, mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock, + mMonotonicClock); return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT); } @@ -333,30 +331,30 @@ public class BatteryUsageStatsProviderTest { BatteryStats.HistoryItem item; assertThat(item = iterator.next()).isNotNull(); - assertHistoryItem(batteryStats, item, + assertHistoryItem(item, BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE, null, 0, 3_600_000, 90, 1_000_000); assertThat(item = iterator.next()).isNotNull(); - assertHistoryItem(batteryStats, item, + assertHistoryItem(item, BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE, null, 0, 3_600_000, 90, 1_000_000); assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isNotEqualTo(0); assertThat(item = iterator.next()).isNotNull(); - assertHistoryItem(batteryStats, item, + assertHistoryItem(item, BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE, null, 0, 3_600_000, 90, 2_000_000); assertThat(item.states & BatteryStats.HistoryItem.STATE_CPU_RUNNING_FLAG).isEqualTo(0); assertThat(item = iterator.next()).isNotNull(); - assertHistoryItem(batteryStats, item, + assertHistoryItem(item, BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START, "foo", APP_UID, 3_600_000, 90, 3_000_000); assertThat(item = iterator.next()).isNotNull(); - assertHistoryItem(batteryStats, item, + assertHistoryItem(item, BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH, "foo", APP_UID, 3_600_000, 90, 3_001_000); @@ -443,15 +441,14 @@ public class BatteryUsageStatsProviderTest { assertThat(item.eventTag.string).startsWith(uid + " "); assertThat(item.batteryChargeUah).isEqualTo(3_600_000); assertThat(item.batteryLevel).isEqualTo(90); - assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + 1_000_000); + assertThat(item.time).isEqualTo((long) 1_000_000); } assertThat(expectedUid).isEqualTo(200); } - private void assertHistoryItem(MockBatteryStatsImpl batteryStats, BatteryStats.HistoryItem item, - int command, int eventCode, String tag, int uid, int batteryChargeUah, int batteryLevel, - long elapsedTimeMs) { + private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode, + String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) { assertThat(item.cmd).isEqualTo(command); assertThat(item.eventCode).isEqualTo(eventCode); if (tag == null) { @@ -463,7 +460,7 @@ public class BatteryUsageStatsProviderTest { assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah); assertThat(item.batteryLevel).isEqualTo(batteryLevel); - assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + elapsedTimeMs); + assertThat(item.time).isEqualTo(elapsedTimeMs); } @Test @@ -569,66 +566,38 @@ public class BatteryUsageStatsProviderTest { assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * MINUTE_IN_MS); - assertBatteryConsumer(stats, 180.0, (10 + 20) * MINUTE_IN_MS); - assertBatteryConsumer(stats, APP_UID, 180.0, (10 + 20) * MINUTE_IN_MS); + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.0001) + .of(180.0); // 360 mA * 0.5 hours + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo((10 + 20) * MINUTE_IN_MS); + final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); + assertThat(uidBatteryConsumer + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.1) + .of(180.0); stats.close(); } @Test public void accumulateBatteryUsageStats() throws Throwable { - MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - accumulateBatteryUsageStats(batteryStats, 10000000, 0); + accumulateBatteryUsageStats(10000000, 1); // Accumulate every 200 bytes of battery history - accumulateBatteryUsageStats(batteryStats, 200, 2); - accumulateBatteryUsageStats(batteryStats, 50, 4); + accumulateBatteryUsageStats(200, 2); + accumulateBatteryUsageStats(50, 5); // Accumulate on every invocation of accumulateBatteryUsageStats - accumulateBatteryUsageStats(batteryStats, 0, 7); + accumulateBatteryUsageStats(0, 7); } - @Test - public void getAccumulatedBatteryUsageStats() throws Throwable { - MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); - - // Only accumulate the first 25 minutes - accumulateBatteryUsageStats(batteryStats, 200, 1); - - BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider(200); - - // At this point the last stored accumulated stats are `115 - 30 = 85` minutes old - BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats, - new BatteryUsageStatsQuery.Builder() - .accumulated() - .setMaxStatsAgeMs(90 * MINUTE_IN_MS) - .build()); - - assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); - assertThat(stats.getStatsEndTimestamp()).isEqualTo(30 * MINUTE_IN_MS); - assertBatteryConsumer(stats, 60.0, 10 * MINUTE_IN_MS); - assertBatteryConsumer(stats, APP_UID, 60.0, 10 * MINUTE_IN_MS); - - stats.close(); - - // Now force the usage stats to catch up to the current time - stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats, - new BatteryUsageStatsQuery.Builder() - .accumulated() - .setMaxStatsAgeMs(5 * MINUTE_IN_MS) - .build()); - - assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); - assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS); - assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS); - assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS); - - stats.close(); - } - - private void accumulateBatteryUsageStats(MockBatteryStatsImpl batteryStatsImpl, - int accumulatedBatteryUsageStatsSpanSize, int expectedNumberOfUpdates) - throws Throwable { - Handler handler = mStatsRule.getHandler(); - MockBatteryStatsImpl batteryStats = spy(batteryStatsImpl); + private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize, + int expectedNumberOfUpdates) throws Throwable { + BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats()); // Note - these two are in microseconds when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L); when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L); @@ -641,76 +610,82 @@ public class BatteryUsageStatsProviderTest { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } - mPowerStatsStore.reset(); + PowerStatsStore powerStatsStore = spy(new PowerStatsStore( + new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()), + mStatsRule.getHandler())); + powerStatsStore.reset(); int[] count = new int[1]; doAnswer(inv -> { count[0]++; - return inv.callRealMethod(); - }).when(mPowerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class)); + return null; + }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class)); - BatteryUsageStatsProvider batteryUsageStatsProvider = createBatteryUsageStatsProvider( - accumulatedBatteryUsageStatsSpanSize); + MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext, + powerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), + () -> 3500); + for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT; + powerComponentId++) { + powerAttributor.setPowerComponentSupported(powerComponentId, true); + } + powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true); + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, + powerAttributor, mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies(), powerStatsStore, + accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock); - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); - setTime(10 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); } - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); - setTime(20 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOffLocked(APP_UID, 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); } - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); - setTime(30 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); } - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); - // Make sure the accumulated stats are computed and saved before generating more history - mStatsRule.waitForBackgroundThread(); - - setTime(50 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOffLocked(APP_UID, 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); } setTime(55 * MINUTE_IN_MS); - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); // This section has not been saved yet, but should be added to the accumulated totals - setTime(80 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOnLocked(APP_UID, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); } - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler()); - setTime(110 * MINUTE_IN_MS); synchronized (batteryStats) { batteryStats.noteFlashlightOffLocked(APP_UID, 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); } setTime(115 * MINUTE_IN_MS); - batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler); + // Pick up the remainder of battery history that has not yet been accumulated + provider.accumulateBatteryUsageStats(batteryStats); mStatsRule.waitForBackgroundThread(); - BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats, + BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, new BatteryUsageStatsQuery.Builder().accumulated().build()); assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); @@ -721,55 +696,29 @@ public class BatteryUsageStatsProviderTest { assertThat(stats.getBatteryCapacity()).isEqualTo(4000); // from PowerProfile // Total: 10 + 20 + 30 = 60 - assertBatteryConsumer(stats, 360.0, 60 * MINUTE_IN_MS); - assertBatteryConsumer(stats, APP_UID, 360.0, 60 * MINUTE_IN_MS); - stats.close(); - - mStatsRule.waitForBackgroundThread(); - - assertThat(count[0]).isEqualTo(expectedNumberOfUpdates); - } - - private BatteryUsageStatsProvider createBatteryUsageStatsProvider( - int accumulatedBatteryUsageStatsSpanSize) { - MultiStatePowerAttributor powerAttributor = new MultiStatePowerAttributor(mContext, - mPowerStatsStore, mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), - () -> 3500); - for (int powerComponentId = 0; powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT; - powerComponentId++) { - powerAttributor.setPowerComponentSupported(powerComponentId, true); - } - powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_ANY, true); - - return new BatteryUsageStatsProvider(mContext, powerAttributor, - mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), mPowerStatsStore, - accumulatedBatteryUsageStatsSpanSize, mMockClock, mMonotonicClock); - } - - private static void assertBatteryConsumer(BatteryUsageStats stats, double expectedPowerMah, - long expectedDurationMs) { - AggregateBatteryConsumer aggregatedConsumer = stats.getAggregateBatteryConsumer( - BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); - assertThat(aggregatedConsumer + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) .isWithin(0.0001) - .of(expectedPowerMah); - assertThat(aggregatedConsumer + .of(360.0); // 360 mA * 1.0 hour + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) - .isEqualTo(expectedDurationMs); - } + .isEqualTo(60 * MINUTE_IN_MS); - private static void assertBatteryConsumer(BatteryUsageStats stats, int uid, - double expectedPowerMah, long expectedDurationMs) { final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() - .filter(u -> u.getUid() == uid).findFirst().get(); + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); assertThat(uidBatteryConsumer .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) .isWithin(0.1) - .of(expectedPowerMah); + .of(360.0); assertThat(uidBatteryConsumer .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) - .isEqualTo(expectedDurationMs); + .isEqualTo(60 * MINUTE_IN_MS); + + assertThat(count[0]).isEqualTo(expectedNumberOfUpdates); + + stats.close(); } private void setTime(long timeMs) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 9e7e0b646047..a3c7ece386c7 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -41,7 +41,6 @@ import android.util.SparseArray; import android.util.Xml; import com.android.internal.os.CpuScalingPolicies; -import com.android.internal.os.MonotonicClock; import com.android.internal.os.PowerProfile; import com.android.internal.power.EnergyConsumerStats; @@ -66,7 +65,6 @@ public class BatteryUsageStatsRule implements TestRule { private final PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); - private final MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock); private String mTestName; private boolean mCreateTempDirectory; private File mHistoryDir; @@ -120,7 +118,7 @@ public class BatteryUsageStatsRule implements TestRule { clearDirectory(); } mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(), - mMockClock, mMonotonicClock, mHistoryDir, mHandler, new PowerStatsUidResolver()); + mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver()); mBatteryStats.setPowerProfile(mPowerProfile); mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); synchronized (mBatteryStats) { @@ -146,10 +144,6 @@ public class BatteryUsageStatsRule implements TestRule { return mMockClock; } - public MonotonicClock getMonotonicClock() { - return mMonotonicClock; - } - public Handler getHandler() { return mHandler; } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 9a38209a7d17..b374a3202fa2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -77,15 +77,9 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { new PowerStatsUidResolver()); } - MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, - File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) { - this(config, clock, new MonotonicClock(0, clock), historyDirectory, handler, - powerStatsUidResolver); - } - - MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, MonotonicClock monotonicClock, - File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) { - super(config, clock, monotonicClock, historyDirectory, handler, + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory, + Handler handler, PowerStatsUidResolver powerStatsUidResolver) { + super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler, mock(PlatformIdleStateCallback.class), mock(EnergyStatsRetriever.class), mock(UserInfoProvider.class), mockPowerProfile(), new CpuScalingPolicies(new SparseArray<>(), new SparseArray<>()), diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java index d243f92a139f..38fe6134d992 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java @@ -408,7 +408,7 @@ public class PowerStatsExporterTest { BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder( new String[]{"cu570m"}, /* includeProcessStateData */ true, true, true, /* powerThreshold */ 0); - exportAggregatedPowerStats(builder, 3700, 7500); + exportAggregatedPowerStats(builder, 3700, 6700); BatteryUsageStats actual = builder.build(); String message = "Actual BatteryUsageStats: " + actual; diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml index 6460a32efc4f..d58e0d84ee32 100644 --- a/services/tests/security/intrusiondetection/AndroidManifest.xml +++ b/services/tests/security/intrusiondetection/AndroidManifest.xml @@ -23,15 +23,6 @@ <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true"> <uses-library android:name="android.test.runner"/> - <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver" - android:permission="android.permission.BIND_DEVICE_ADMIN" - android:exported="true"> - <meta-data android:name="android.app.device_admin" - android:resource="@xml/device_admin"/> - <intent-filter> - <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/> - </intent-filter> - </receiver> </application> <queries> diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/services/tests/security/intrusiondetection/res/xml/device_admin.xml deleted file mode 100644 index f8cd8f0b9b44..000000000000 --- a/services/tests/security/intrusiondetection/res/xml/device_admin.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<device-admin xmlns:android="http://schemas.android.com/apk/res/android"> -</device-admin> diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java index 1380a6fec94f..5cba6b2c3e67 100644 --- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java +++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java @@ -16,13 +16,12 @@ package com.android.server.security.intrusiondetection; +import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE; import static android.Manifest.permission.INTERNET; import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE; import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE; -import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -45,9 +44,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.Manifest; import android.os.IBinder; -import android.os.Bundle; import android.os.Looper; import android.os.PermissionEnforcer; import android.os.RemoteException; @@ -63,17 +60,13 @@ import android.util.Log; import androidx.test.core.app.ApplicationProvider; import com.android.bedstead.harrier.BedsteadJUnit4; -import com.android.bedstead.harrier.annotations.AfterClass; -import com.android.bedstead.harrier.annotations.BeforeClass; import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser; import com.android.bedstead.nene.TestApis; import com.android.bedstead.nene.devicepolicy.DeviceOwner; -import com.android.bedstead.nene.exceptions.NeneException; import com.android.bedstead.permissions.CommonPermissions; import com.android.bedstead.permissions.PermissionContext; import com.android.bedstead.permissions.annotations.EnsureHasPermission; import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport; -import com.android.internal.infra.AndroidFuture; import com.android.server.ServiceThread; import org.junit.Before; @@ -131,28 +124,6 @@ public class IntrusionDetectionServiceTest { "com.android.coretests.apps.testapp"; private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService"; - @BeforeClass - public static void setDeviceOwner() { - ComponentName admin = - new ComponentName( - ApplicationProvider.getApplicationContext(), - IntrusionDetectionAdminReceiver.class); - try { - sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin); - } catch (NeneException e) { - fail("Failed to set device owner " + admin.flattenToString() + ": " + e); - } - } - - @AfterClass - public static void removeDeviceOwner() { - try { - sDeviceOwner.remove(); - } catch (NeneException e) { - fail("Failed to remove device owner : " + e); - } - } - @SuppressLint("VisibleForTests") @Before public void setUp() throws Exception { @@ -181,6 +152,7 @@ public class IntrusionDetectionServiceTest { } @Test + @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testRemoveStateCallback_NoPermission() { mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE); StateCallback scb = new StateCallback(); @@ -232,6 +204,7 @@ public class IntrusionDetectionServiceTest { } @Test + @Ignore("Unit test does not run as system service UID") public void testRemoveStateCallback() throws RemoteException { mIntrusionDetectionService.setState(STATE_DISABLED); StateCallback scb1 = new StateCallback(); @@ -242,7 +215,6 @@ public class IntrusionDetectionServiceTest { assertEquals(STATE_DISABLED, scb1.mState); assertEquals(STATE_DISABLED, scb2.mState); - doReturn(true).when(mDataAggregator).initialize(); doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize(); mIntrusionDetectionService.getBinderService().removeStateCallback(scb2); @@ -255,6 +227,7 @@ public class IntrusionDetectionServiceTest { assertNull(ccb.mErrorCode); } + @Ignore("Unit test does not run as system service UID") @Test public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException { mIntrusionDetectionService.setState(STATE_DISABLED); @@ -415,39 +388,13 @@ public class IntrusionDetectionServiceTest { } @Test - @RequireRunOnSystemUser - public void testDataSources_Initialize_HasDeviceOwner() throws Exception { - NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator); - SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator); - - assertTrue(networkLogSource.initialize()); - assertTrue(securityLogSource.initialize()); - } - - @Test - @RequireRunOnSystemUser - public void testDataSources_Initialize_NoDeviceOwner() throws Exception { - NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator); - SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator); - ComponentName admin = sDeviceOwner.componentName(); - - try { - sDeviceOwner.remove(); - assertFalse(networkLogSource.initialize()); - assertFalse(securityLogSource.initialize()); - } finally { - sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin); - } - } - - @Test + @Ignore("Unit test does not run as system service UID") @RequireRunOnSystemUser @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testDataAggregator_AddSecurityEvent() throws Exception { mIntrusionDetectionService.setState(STATE_ENABLED); ServiceThread mockThread = spy(ServiceThread.class); mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); - assertTrue(mDataAggregator.initialize()); // SecurityLogging generates a number of events and callbacks, so create a latch to wait for // the given event. @@ -491,12 +438,12 @@ public class IntrusionDetectionServiceTest { @Test @RequireRunOnSystemUser + @Ignore("Unit test does not run as system service UID") @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void testDataAggregator_AddNetworkEvent() throws Exception { mIntrusionDetectionService.setState(STATE_ENABLED); ServiceThread mockThread = spy(ServiceThread.class); mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); - assertTrue(mDataAggregator.initialize()); // Network logging may log multiple and callbacks, so create a latch to wait for // the given event. diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index 0404b82d306d..c4b8599a483c 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -118,6 +118,7 @@ import org.mockito.Spy; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -232,7 +233,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { any(AlarmManager.OnAlarmListener.class), any(Handler.class)); doAnswer(inv -> { - mCustomListener = () -> {}; + mCustomListener = () -> { + }; return null; }).when(mAlarmManager).cancel(eq(mCustomListener)); when(mContext.getSystemService(eq(Context.POWER_SERVICE))) @@ -1321,7 +1323,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void enableCarMode_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, () -> mService.enableCarMode(0, 0, PACKAGE_NAME)); assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR); @@ -1343,19 +1345,19 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { @Test public void disableCarMode_failsForBogusPackageName() throws Exception { when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.DEFAULT_CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.enableCarMode(0, 0, PACKAGE_NAME); assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR); when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); + .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1); assertThrows(SecurityException.class, - () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME)); + () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME)); assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR); // Clean up when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())) - .thenReturn(TestInjector.DEFAULT_CALLING_UID); + .thenReturn(TestInjector.DEFAULT_CALLING_UID); mService.disableCarModeByCallingPackage(0, PACKAGE_NAME); assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR); } @@ -1473,12 +1475,13 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { assertFalse(mUiManagerService.getConfiguration().isNightModeActive()); } - // attention modes with expected night modes - Map<Integer, Boolean> modes = Map.of( - MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode, - MODE_ATTENTION_THEME_OVERLAY_DAY, false, - MODE_ATTENTION_THEME_OVERLAY_NIGHT, true - ); + // Attention modes with expected night modes. + // Important to keep modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode) in the + // first position, hence LinkedHashMap. + Map<Integer, Boolean> modes = new LinkedHashMap<>(); + modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode); + modes.put(MODE_ATTENTION_THEME_OVERLAY_DAY, false); + modes.put(MODE_ATTENTION_THEME_OVERLAY_NIGHT, true); // test for (int attentionMode : modes.keySet()) { @@ -1562,7 +1565,7 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { private final int callingUid; public TestInjector() { - this(DEFAULT_CALLING_UID); + this(DEFAULT_CALLING_UID); } public TestInjector(int callingUid) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 90bf1d36a4ce..074cbb57d5b7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -49,10 +49,14 @@ import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; +import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; +import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; +import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID; +import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS; import static android.app.NotificationManager.EXTRA_BLOCKED_STATE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -369,6 +373,7 @@ import java.io.FileOutputStream; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -11273,19 +11278,71 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(102))); } + @Test + @EnableFlags(android.app.Flags.FLAG_MODES_API) + public void onAutomaticRuleStatusChanged_sendsBroadcastToRuleOwner() throws Exception { + mService.mZenModeHelper.getCallbacks().forEach(c -> c.onAutomaticRuleStatusChanged( + mUserId, "rule.owner.pkg", "rule_id", AUTOMATIC_RULE_STATUS_ACTIVATED)); + + Intent expected = new Intent(ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED) + .setPackage("rule.owner.pkg") + .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, "rule_id") + .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS, AUTOMATIC_RULE_STATUS_ACTIVATED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + + verify(mContext).sendBroadcastAsUser(eqIntent(expected), eq(UserHandle.of(mUserId))); + } + private static Intent eqIntent(Intent wanted) { return ArgumentMatchers.argThat( new ArgumentMatcher<Intent>() { @Override public boolean matches(Intent argument) { return wanted.filterEquals(argument) - && wanted.getFlags() == argument.getFlags(); + && wanted.getFlags() == argument.getFlags() + && equalBundles(wanted.getExtras(), argument.getExtras()); } @Override public String toString() { return wanted.toString(); } + + private boolean equalBundles(Bundle one, Bundle two) { + if (one == null && two == null) { + return true; + } + if ((one == null) != (two == null)) { + return false; + } + if (one.size() != two.size()) { + return false; + } + + HashSet<String> setOne = new HashSet<>(one.keySet()); + setOne.addAll(two.keySet()); + + for (String key : setOne) { + if (!one.containsKey(key) || !two.containsKey(key)) { + return false; + } + + Object valueOne = one.get(key); + Object valueTwo = two.get(key); + if (valueOne instanceof Bundle + && valueTwo instanceof Bundle + && !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) { + return false; + } else if (valueOne == null) { + if (valueTwo != null) { + return false; + } + } else if (!valueOne.equals(valueTwo)) { + return false; + } + } + return true; + } }); } @@ -17314,6 +17371,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); @@ -17327,6 +17385,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0, @@ -17339,6 +17398,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0, @@ -17388,6 +17448,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); @@ -17420,6 +17481,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); @@ -17434,6 +17496,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); @@ -17483,6 +17546,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post .build(); @@ -17515,6 +17579,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, n, UserHandle.getUserHandleForUid(mUid), null, 0); @@ -17543,6 +17608,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, @@ -17570,6 +17636,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) .setColor(Color.WHITE) .setColorized(true) + .setOngoing(true) .build(); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 1e9038ee1769..32ba8f5f64d1 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -468,6 +468,14 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test + public void testKeyGestureLaunchVoiceAssistant() { + Assert.assertTrue( + sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT)); + mPhoneWindowManager.assertSearchManagerLaunchAssist(); + } + + @Test public void testKeyGestureGoHome() { Assert.assertTrue( sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java index 2a53df9f8353..a7fc10f2fcc5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java @@ -17,30 +17,23 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SnapshotPersistQueue.MAX_STORE_QUEUE_DEPTH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import android.content.ComponentName; -import android.graphics.ColorSpace; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.HardwareBuffer; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; -import android.view.Surface; import android.window.TaskSnapshot; import androidx.test.filters.SmallTest; @@ -61,14 +54,20 @@ import java.util.Arrays; @SmallTest @Presubmit @RunWith(WindowTestRunner.class) -public class ActivitySnapshotControllerTests extends WindowTestsBase { +public class ActivitySnapshotControllerTests extends TaskSnapshotPersisterTestBase { private ActivitySnapshotController mActivitySnapshotController; + public ActivitySnapshotControllerTests() { + super(0.8f /* highResScale */, 0.5f /* lowResScale */); + } + + @Override @Before - public void setUp() throws Exception { - spyOn(mWm.mSnapshotController.mActivitySnapshotController); - mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController; + public void setUp() { + super.setUp(); + mActivitySnapshotController = new ActivitySnapshotController(mWm, mSnapshotPersistQueue); + spyOn(mActivitySnapshotController); doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots(); mActivitySnapshotController.resetTmpFields(); } @@ -92,12 +91,11 @@ public class ActivitySnapshotControllerTests extends WindowTestsBase { assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size()); mActivitySnapshotController.resetTmpFields(); - // simulate three activity + // simulate three activity, the bottom activity won't participate in transition final WindowState belowClose = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "belowClose"); belowClose.mActivityRecord.commitVisibility( false /* visible */, true /* performLayout */); - windows.add(belowClose.mActivityRecord); mActivitySnapshotController.handleTransitionFinish(windows); assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size()); assertEquals(belowClose.mActivityRecord, @@ -249,15 +247,28 @@ public class ActivitySnapshotControllerTests extends WindowTestsBase { assertEquals(taskSnapshot, mActivitySnapshotController.getSnapshot(activities)); } - private TaskSnapshot createSnapshot() { - HardwareBuffer buffer = mock(HardwareBuffer.class); - doReturn(100).when(buffer).getWidth(); - doReturn(100).when(buffer).getHeight(); - return new TaskSnapshot(1, 0 /* captureTime */, new ComponentName("", ""), buffer, - ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT, - Surface.ROTATION_0, new Point(100, 100), new Rect() /* contentInsets */, - new Rect() /* letterboxInsets*/, false /* isLowResolution */, - true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN, 0 /* mSystemUiVisibility */, - false /* isTranslucent */, false /* hasImeSurface */, 0 /* uiMode */); + /** + * Verifies that activity snapshot is skipped if the persister queue has too many pending write + * items. + */ + @Test + public void testSkipRecordActivity() { + doReturn(createSnapshot()).when(mActivitySnapshotController).recordSnapshotInner(any()); + final Task task = createTask(mDisplayContent); + + mSnapshotPersistQueue.setPaused(true); + final ArrayList<ActivityRecord> tmpList = new ArrayList<>(); + for (int i = 0; i < MAX_STORE_QUEUE_DEPTH; ++i) { + tmpList.clear(); + final ActivityRecord activity = createActivityRecord(task); + tmpList.add(activity); + mActivitySnapshotController.recordSnapshot(tmpList); + assertNotNull(mActivitySnapshotController.findSavedFile(activity)); + } + tmpList.clear(); + final ActivityRecord activity = createActivityRecord(task); + tmpList.add(activity); + mActivitySnapshotController.recordSnapshot(tmpList); + assertNull(mActivitySnapshotController.findSavedFile(activity)); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index aa992504f9a5..25b9f4b8035b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -717,13 +717,13 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents() { testVisibleTasks_excludedFromRecents_internal(); } @Test - @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_withRefactorFlag() { testVisibleTasks_excludedFromRecents_internal(); } @@ -767,13 +767,13 @@ public class RecentTasksTest extends WindowTestsBase { @Test @Ignore("b/342627272") - @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() { testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); } @Test - @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() { testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); } @@ -816,13 +816,13 @@ public class RecentTasksTest extends WindowTestsBase { } @Test - @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @DisableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() { testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); } @Test - @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + @EnableFlags(Flags.FLAG_ENABLE_USE_TOP_VISIBLE_ACTIVITY_FOR_EXCLUDE_FROM_RECENT_TASK) public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() { testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index d183cf720491..f3a2e86fe6db 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -46,6 +46,7 @@ import android.app.servertransaction.WindowContextInfoChangeItem; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.Display; import android.view.DisplayInfo; @@ -54,7 +55,12 @@ import android.window.WindowTokenClient; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + +import com.google.common.truth.Expect; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -65,6 +71,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; + /** * Build/Install/Run: * atest WmTests:WindowContextListenerControllerTests @@ -86,6 +93,9 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { private WindowProcessController mWpc; private WindowContainer<?> mContainer; + @Rule + public final Expect mExpect = Expect.create(); + @Before public void setUp() { initMocks(this); @@ -341,6 +351,30 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId); } + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void assertCallerCanReparentListener_returnsTrueWhenExpected() { + mController.registerWindowContainerListener(mWpc, mClientToken, mContainer, + TYPE_APPLICATION_OVERLAY, null /* options */); + + // Here there are several checks in one test as wm tests are expensive. + + // Correct conditions -> returns true + mExpect.that(mController.assertCallerCanReparentListener(mClientToken, + /* callerCanManageAppTokens= */ true, + /* callingUid= */ mWpc.mUid, + /* displayId= */ DEFAULT_DISPLAY + 1 + )).isTrue(); + + // sameDisplayId (so, container already attached) -> returnsFalse + mExpect.that(mController.assertCallerCanReparentListener( + mClientToken, + /* callerCanManageAppTokens= */ true, + /* callingUid= */ mWpc.mUid, + /* displayId= */ DEFAULT_DISPLAY // <- same display ID + )).isFalse(); + } + private static class TestWindowTokenClient extends WindowTokenClient { private Configuration mConfiguration; private int mDisplayId; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index bfa6cb820a42..69df66ee783b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION; import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST; @@ -70,6 +71,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.description; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -86,10 +88,10 @@ import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; import android.util.MergedConfiguration; import android.view.ContentRecordingSession; @@ -151,9 +153,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Rule public Expect mExpect = Expect.create(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - @After public void tearDown() { mWm.mSensitiveContentPackages.clearBlockedApps(); @@ -1412,6 +1411,84 @@ public class WindowManagerServiceTests extends WindowTestsBase { assertThat(result).isEqualTo(WindowManagerGlobal.ADD_INVALID_DISPLAY); } + /** Mocks some deps to associate a display content to a specific display id. */ + private void setupReparentWindowContextToDisplayAreaTest(WindowToken windowToken, + DisplayContent dc, int displayId) { + spyOn(mWm.mWindowContextListenerController); + doReturn(dc).when(mWm.mRoot).getDisplayContentOrCreate(displayId); + doReturn(true).when(mWm.mWindowContextListenerController).assertCallerCanReparentListener( + any(), anyBoolean(), anyInt(), eq(displayId)); + doReturn(windowToken).when(mWm.mWindowContextListenerController).getContainer( + eq(windowToken.token)); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentWindowContextToDisplayArea_newDisplay_reparented() { + final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE, + mDisplayContent); + final int newDisplayId = 1; + final DisplayContent dc = createNewDisplay(); + setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId); + + assertThat(windowToken.getDisplayContent()).isEqualTo(mDisplayContent); + + assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token, + newDisplayId)).isTrue(); + + assertThat(windowToken.getDisplayContent()).isNotEqualTo(mDisplayContent); + } + + @Test + @DisableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentWindowContextToDisplayArea_newDisplayButFlagDisabled_notReparented() { + final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE, + mDisplayContent); + final int newDisplayId = 1; + final DisplayContent dc = createNewDisplay(); + setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId); + + assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token, + newDisplayId)).isFalse(); + + assertThat(windowToken.getDisplayContent()).isEqualTo(mDisplayContent); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentWindowContext_afterReparent_DCNeedsLayout() { + final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE, + mDisplayContent); + final int newDisplayId = 1; + final DisplayContent dc = createNewDisplay(); + setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId); + + assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token, + newDisplayId)).isTrue(); + + assertThat(dc.isLayoutNeeded()).isTrue(); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void reparentWindowContext_afterReparent_traversalScheduled() { + final WindowToken windowToken = createTestClientWindowToken(TYPE_NOTIFICATION_SHADE, + mDisplayContent); + final int newDisplayId = 1; + final DisplayContent dc = createNewDisplay(); + setupReparentWindowContextToDisplayAreaTest(windowToken, dc, newDisplayId); + spyOn(mWm.mWindowPlacerLocked); + reset(mWm.mWindowPlacerLocked); + + verify(mWm.mWindowPlacerLocked, never()).requestTraversal(); + + assertThat(mWm.reparentWindowContextToDisplayArea(mAppThread, windowToken.token, + newDisplayId)).isTrue(); + + verify(mWm.mWindowPlacerLocked).requestTraversal(); + } + + class TestResultReceiver implements IResultReceiver { public android.os.Bundle resultData; private final IBinder mBinder = mock(IBinder.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 78e6cbf9c36a..3a97cc621e0d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -2039,9 +2039,22 @@ public class WindowTestsBase extends SystemServiceTestsBase { return new TestWindowToken(type, dc, persistOnEmpty); } + static TestWindowToken createTestClientWindowToken(int type, DisplayContent dc) { + SystemServicesTestRule.checkHoldsLock(dc.mWmService.mGlobalLock); + + return new TestWindowToken(type, dc, false /* persistOnEmpty */, true /* fromClient */); + } + /** Used so we can gain access to some protected members of the {@link WindowToken} class */ static class TestWindowToken extends WindowToken { + private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty, + boolean fromClient) { + super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc, + false /* ownerCanManageAppTokens */, false /* roundedCornerOverlay */, + fromClient /* fromClientToken */, null /* options */); + } + private TestWindowToken(int type, DisplayContent dc, boolean persistOnEmpty) { super(dc.mWmService, mock(IBinder.class), type, persistOnEmpty, dc, false /* ownerCanManageAppTokens */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 35328a0e1dc0..f226b9d29ca0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -33,21 +33,27 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.WindowInsets; import android.window.WindowContext; import androidx.test.filters.SmallTest; +import com.android.window.flags.Flags; + import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import java.util.function.BiFunction; @@ -304,14 +310,39 @@ public class WindowTokenTests extends WindowTestsBase { // immediately. verify the window will hide without applying exit animation. mWm.removeWindowToken(win.mToken.token, false /* removeWindows */, false /* animateExit */, mDisplayContent.mDisplayId); - verify(win).onSetAppExiting(Mockito.eq(false) /* animateExit */); + verify(win).onSetAppExiting(eq(false) /* animateExit */); verify(win).hide(false /* doAnimation */, false /* requestAnim */); assertFalse(win.isOnScreen()); - verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false); + verify(win.mWinAnimator, never()).applyAnimationLocked(TRANSIT_EXIT, false); assertTrue(win.mToken.hasChild()); // Even though the window is being removed afterwards, it won't apply exit animation. win.removeIfPossible(); - verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false); + verify(win.mWinAnimator, never()).applyAnimationLocked(TRANSIT_EXIT, false); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void onDisplayChanged_differentDisplay_reparented() { + final TestWindowToken token = createTestWindowToken(0, mDisplayContent); + final DisplayContent dc = mock(DisplayContent.class); + when(dc.getWindowToken(any())).thenReturn(null); // dc doesn't have this window token. + + token.onDisplayChanged(dc); + + verify(dc).reParentWindowToken(eq(token)); + } + + @Test + @EnableFlags(Flags.FLAG_REPARENT_WINDOW_TOKEN_API) + public void onDisplayChanged_samedisplay_notReparented() { + + final TestWindowToken token = createTestWindowToken(0, mDisplayContent); + final DisplayContent dc = mock(DisplayContent.class); + when(dc.getWindowToken(any())).thenReturn(mock(WindowToken.class)); + + token.onDisplayChanged(dc); + + verify(dc, never()).reParentWindowToken(eq(token)); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 010a32274dcd..e7c9e927b311 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -48,6 +48,7 @@ import android.app.IUidObserver; import android.app.PendingIntent; import android.app.UidObserver; import android.app.admin.DevicePolicyManagerInternal; +import android.app.supervision.SupervisionManagerInternal; import android.app.usage.AppLaunchEstimateInfo; import android.app.usage.AppStandbyInfo; import android.app.usage.BroadcastResponseStatsList; @@ -230,6 +231,7 @@ public class UsageStatsService extends SystemService implements // Do not use directly. Call getDpmInternal() instead DevicePolicyManagerInternal mDpmInternal; // Do not use directly. Call getShortcutServiceInternal() instead + SupervisionManagerInternal mSupervisionManagerInternal; ShortcutServiceInternal mShortcutServiceInternal; private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); @@ -439,6 +441,9 @@ public class UsageStatsService extends SystemService implements // initialize mDpmInternal getDpmInternal(); // initialize mShortcutServiceInternal + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + getSupervisionManagerInternal(); + } getShortcutServiceInternal(); mResponseStatsTracker.onSystemServicesReady(getContext()); @@ -604,6 +609,15 @@ public class UsageStatsService extends SystemService implements return mDpmInternal; } + @Nullable + private SupervisionManagerInternal getSupervisionManagerInternal() { + if (mSupervisionManagerInternal == null) { + mSupervisionManagerInternal = + LocalServices.getService(SupervisionManagerInternal.class); + } + return mSupervisionManagerInternal; + } + private ShortcutServiceInternal getShortcutServiceInternal() { if (mShortcutServiceInternal == null) { mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class); @@ -753,6 +767,16 @@ public class UsageStatsService extends SystemService implements callingPid, callingUid) == PackageManager.PERMISSION_GRANTED); } + private boolean isSupervisionEnabled(int callingUid) { + if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) { + SupervisionManagerInternal smInternal = getSupervisionManagerInternal(); + return smInternal != null && smInternal.isActiveSupervisionApp(callingUid); + } else { + DevicePolicyManagerInternal dpmInternal = getDpmInternal(); + return dpmInternal != null && dpmInternal.isActiveSupervisionApp(callingUid); + } + } + private static void deleteRecursively(final File path) { if (path.isDirectory()) { final File[] files = path.listFiles(); @@ -2929,10 +2953,9 @@ public class UsageStatsService extends SystemService implements long timeLimitMs, long timeUsedMs, PendingIntent callbackIntent, String callingPackage) { final int callingUid = Binder.getCallingUid(); - final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (!hasPermissions( Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE) - && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) { + && !isSupervisionEnabled(callingUid)) { throw new SecurityException("Caller must be the active supervision app or " + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions"); } @@ -2956,10 +2979,9 @@ public class UsageStatsService extends SystemService implements @Override public void unregisterAppUsageLimitObserver(int observerId, String callingPackage) { final int callingUid = Binder.getCallingUid(); - final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (!hasPermissions( Manifest.permission.SUSPEND_APPS, Manifest.permission.OBSERVE_APP_USAGE) - && (dpmInternal == null || !dpmInternal.isActiveSupervisionApp(callingUid))) { + && !isSupervisionEnabled(callingUid)) { throw new SecurityException("Caller must be the active supervision app or " + "it must have both SUSPEND_APPS and OBSERVE_APP_USAGE permissions"); } diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java index a69dfb0b255f..cdb3eaf46def 100644 --- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java +++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java @@ -16,12 +16,12 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; /** @@ -255,6 +255,11 @@ public class ParcelableCallAnalytics implements Parcelable { public static final int CALLTYPE_OUTGOING = 2; // Constants for call technology + /** + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_PHONE = 0x1; public static final int GSM_PHONE = 0x2; public static final int IMS_PHONE = 0x4; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index d89c9c16eb09..7082f0028a5e 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2049,11 +2049,15 @@ public class TelecomManager { /** * Ends the foreground call on the device. * <p> - * If there is a ringing call, calling this method rejects the ringing call. Otherwise the + * If there is a ringing call, calling this method rejects the ringing call. Otherwise, the * foreground call is ended. * <p> * Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false} * if an attempt is made to end an emergency call. + * <p> + * Note: If the foreground call on this device is self-managed, this method will only end + * the call if the caller of this method is privileged (i.e. system, shell, or root) or system + * UI. * * @return {@code true} if there is a call which will be rejected or terminated, {@code false} * otherwise. @@ -2082,6 +2086,9 @@ public class TelecomManager { * the incoming call requests. This means, for example, that an incoming call requesting * {@link VideoProfile#STATE_BIDIRECTIONAL} will be answered, accepting that state. * + * If the ringing incoming call is self-managed, this method will only accept the call if the + * caller of this method is privileged (i.e. system, shell, or root) or system UI. + * * @deprecated Companion apps for wearable devices should use the {@link InCallService} API * instead. */ diff --git a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl index eda0f5b24958..c4a3670e4f14 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl @@ -24,5 +24,5 @@ import com.android.internal.telecom.IInternalServiceRetriever; * Allows the TelecomLoaderService to pass additional dependencies required for creation. */ interface ITelecomLoader { - ITelecomService createTelecomService(IInternalServiceRetriever retriever); + ITelecomService createTelecomService(IInternalServiceRetriever retriever, String sysUiName); } diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 8fe107cc1ad3..09b18b65be4a 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -109,6 +109,7 @@ public class Annotation { //TelephonyManager.NETWORK_TYPE_LTE_CA, TelephonyManager.NETWORK_TYPE_NR, + TelephonyManager.NETWORK_TYPE_NB_IOT_NTN, }) @Retention(RetentionPolicy.SOURCE) public @interface NetworkType { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 59cb5ff37874..478ec5c62bc6 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -317,8 +317,10 @@ public class CarrierConfigManager { * If this is set as false and the supplementary service menu is visible, the associated setting * will be enabled and disabled based on the availability of supplementary services over UT. See * {@link #KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_SUPPORT_SS_OVER_CDMA_BOOL = "support_ss_over_cdma_bool"; /** @@ -536,7 +538,11 @@ public class CarrierConfigManager { */ public static final String KEY_4G_ONLY_BOOL = "4g_only_bool"; - /** Show cdma network mode choices 1x, 3G, global etc. */ + /** Show cdma network mode choices 1x, 3G, global etc. + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; /** CDMA activation goes through HFA */ @@ -544,9 +550,12 @@ public class CarrierConfigManager { /** * CDMA activation goes through OTASP. + * @deprecated Legacy CDMA is unsupported. */ // TODO: This should be combined with config_use_hfa_for_provisioning and implemented as an enum // (NONE, HFA, OTASP). + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; @@ -556,10 +565,20 @@ public class CarrierConfigManager { /** Does not display additional call setting for IMS phone based on GSM Phone */ public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; - /** Show APN Settings for some CDMA carriers */ + /** + * Show APN Settings for some CDMA carriers + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; - /** After a CDMA conference call is merged, the swap button should be displayed. */ + /** + * After a CDMA conference call is merged, the swap button should be displayed. + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; /** @@ -597,7 +616,10 @@ public class CarrierConfigManager { /** * Disables dialing "*228" (OTASP provisioning) on CDMA carriers where it is not supported or is * potentially harmful by locking the SIM to 3G. + * @deprecated Legacy CDMA is unsupported. */ + @Deprecated + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; @@ -675,14 +697,20 @@ public class CarrierConfigManager { /** * Override the platform's notion of a network operator being considered roaming. * Value is string array of SIDs to be considered roaming for 3GPP2 RATs. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; /** * Override the platform's notion of a network operator being considered non roaming. * Value is string array of SIDs to be considered not roaming for 3GPP2 RATs. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; @@ -1243,8 +1271,10 @@ public class CarrierConfigManager { /** * CDMA carrier ERI (Enhanced Roaming Indicator) file name + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CARRIER_ERI_FILE_NAME_STRING = "carrier_eri_file_name_string"; /* The following 3 fields are related to carrier visual voicemail. */ @@ -1386,7 +1416,10 @@ public class CarrierConfigManager { * Specifies the amount of gap to be added in millis between postdial DTMF tones. When a * non-zero value is specified, the UE shall wait for the specified amount of time before it * sends out successive DTMF tones on the network. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; /** @@ -1804,8 +1837,10 @@ public class CarrierConfigManager { * If this bit is not set, the carrier name display string will be selected from the carrier * display name resolver which doesn't apply the ERI rules. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_ALLOW_ERI_BOOL = "allow_cdma_eri_bool"; /** @@ -1849,8 +1884,10 @@ public class CarrierConfigManager { * If true, then the registered PLMN name (only for CDMA/CDMA-LTE and only when not roaming) * will be #KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING. If false, or if phone type is not * CDMA/CDMA-LTE or if roaming, then #KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING will be ignored. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CDMA_HOME_REGISTERED_PLMN_NAME_OVERRIDE_BOOL = "cdma_home_registered_plmn_name_override_bool"; @@ -1858,8 +1895,10 @@ public class CarrierConfigManager { * String to identify registered PLMN name in CarrierConfig app. This string overrides * registered PLMN name if #KEY_CDMA_HOME_REGISTERED_PLMN_NAME_OVERRIDE_BOOL is true, phone type * is CDMA/CDMA-LTE and device is not in roaming state; otherwise, it will be ignored. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CDMA_HOME_REGISTERED_PLMN_NAME_STRING = "cdma_home_registered_plmn_name_string"; @@ -2440,7 +2479,10 @@ public class CarrierConfigManager { * For carriers which require an empty flash to be sent before sending the normal 3-way calling * flash, the duration in milliseconds of the empty flash to send. When {@code 0}, no empty * flash is sent. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int"; /** @@ -2454,14 +2496,21 @@ public class CarrierConfigManager { * @see TelephonyManager#CDMA_ROAMING_MODE_HOME * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED * @see TelephonyManager#CDMA_ROAMING_MODE_ANY + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; /** * Determines whether 1X voice calls is supported for some CDMA carriers. * Default value is true. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi public static final String KEY_SUPPORT_CDMA_1X_VOICE_CALLS_BOOL = "support_cdma_1x_voice_calls_bool"; @@ -2483,8 +2532,10 @@ public class CarrierConfigManager { /** * Report IMEI as device id even if it's a CDMA/LTE phone. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_FORCE_IMEI_BOOL = "force_imei_bool"; /** @@ -3217,8 +3268,10 @@ public class CarrierConfigManager { * on a 3GPP network. Specifically *67<number> will be converted to #31#<number> and * *82<number> will be converted to *31#<number> before dialing a call when this key is * set TRUE and device is roaming on a 3GPP network. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL = "convert_cdma_caller_id_mmi_codes_while_roaming_on_3gpp_bool"; @@ -3621,8 +3674,11 @@ public class CarrierConfigManager { /** * Support for the original string display of CDMA MO call. * By default, it is disabled. + * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL = "config_show_orig_dial_string_for_cdma"; @@ -5070,8 +5126,10 @@ public class CarrierConfigManager { * The default values come from 3GPP2 C.R1001 table 8.1-1. * Enhanced Roaming Indicator Number Assignments * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY = "cdma_enhanced_roaming_indicator_for_home_network_int_array"; @@ -9756,9 +9814,8 @@ public class CarrierConfigManager { * }</pre> * <p> * This config is empty by default. - * @hide */ - @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE = "regional_satellite_earfcn_bundle"; @@ -9885,15 +9942,14 @@ public class CarrierConfigManager { "remove_satellite_plmn_in_manual_network_scan_bool"; /** - * This value is used to set the max datagram size, if the value is not available then the - * default one will be used. - * If key is {@code true}, retrieve the max datagram value and use this value always, - * {@code false} the default value from the modem will be used. + * This value is used to set the max datagram size in bytes. + * If the value is not available then the default value will be used. * - * @hide + * The default value is 255 bytes. */ - public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE = - "satellite_sos_max_datagram_size"; + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) + public static final String KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT = + "satellite_sos_max_datagram_size_bytes_int"; /** @hide */ @IntDef({ @@ -10062,9 +10118,9 @@ public class CarrierConfigManager { /** * The display name that will be used for satellite functionality within the UI. - * The default string value for this is "Satellite". - * @hide + * The default string value is empty string. */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_DISPLAY_NAME_STRING = "satellite_display_name_string"; /** @@ -10183,10 +10239,8 @@ public class CarrierConfigManager { * A string array containing the list of messaging apps that support satellite. * * The default value contains only "com.google.android.apps.messaging" - * - * @hide */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY = "satellite_supported_msg_apps_string_array"; @@ -11458,7 +11512,7 @@ public class CarrierConfigManager { sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3}); sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1); sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1); - sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE, 255); + sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT, 255); } /** diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java index 14de2f285756..60f986c684fe 100644 --- a/telephony/java/android/telephony/CellBroadcastService.java +++ b/telephony/java/android/telephony/CellBroadcastService.java @@ -17,6 +17,7 @@ package android.telephony; import android.annotation.CallSuper; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -28,6 +29,7 @@ import android.os.IBinder; import android.os.RemoteCallback; import android.telephony.cdma.CdmaSmsCbProgramData; +import com.android.internal.telephony.flags.Flags; import com.android.internal.util.FastPrintWriter; import java.io.FileDescriptor; @@ -88,9 +90,12 @@ public abstract class CellBroadcastService extends Service { * @param slotIndex the index of the slot which received the message * @param bearerData the CDMA SMS bearer data * @param serviceCategory the CDMA SCPT service category + * @deprecated Legacy CDMA is unsupported. */ - public abstract void onCdmaCellBroadcastSms(int slotIndex, @NonNull byte[] bearerData, - @CdmaSmsCbProgramData.Category int serviceCategory); + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated + public void onCdmaCellBroadcastSms(int slotIndex, @NonNull byte[] bearerData, + @CdmaSmsCbProgramData.Category int serviceCategory) {} /** * Handle a CDMA cell broadcast SMS message forwarded from the system. @@ -102,10 +107,13 @@ public abstract class CellBroadcastService extends Service { * @param callback a callback to run after each cell broadcast receiver has handled * the SCP message. The bundle will contain a non-separated * dial string as and an ArrayList of {@link CdmaSmsCbProgramResults}. + * @deprecated Legacy CDMA is unsupported. */ - public abstract void onCdmaScpMessage(int slotIndex, + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated + public void onCdmaScpMessage(int slotIndex, @NonNull List<CdmaSmsCbProgramData> smsCbProgramData, - @NonNull String originatingAddress, @NonNull Consumer<Bundle> callback); + @NonNull String originatingAddress, @NonNull Consumer<Bundle> callback) {} /** * Get broadcasted area information. diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java index 5eace5433128..4e5a246ef773 100644 --- a/telephony/java/android/telephony/CellIdentityCdma.java +++ b/telephony/java/android/telephony/CellIdentityCdma.java @@ -18,11 +18,13 @@ package android.telephony; import static android.text.TextUtils.formatSimple; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.telephony.cdma.CdmaCellLocation; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; import com.android.telephony.Rlog; @@ -30,7 +32,11 @@ import java.util.Objects; /** * CellIdentity is to represent a unique CDMA cell + * + * @deprecated Legacy CDMA is unsupported. */ +@FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) +@Deprecated public final class CellIdentityCdma extends CellIdentity { private static final String TAG = CellIdentityCdma.class.getSimpleName(); private static final boolean DBG = false; @@ -124,7 +130,11 @@ public final class CellIdentityCdma extends CellIdentity { return new CellIdentityCdma(this); } - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public @NonNull CellIdentityCdma sanitizeLocationInfo() { return new CellIdentityCdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, @@ -157,7 +167,11 @@ public final class CellIdentityCdma extends CellIdentity { /** * @return Network Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} * if unavailable. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public int getNetworkId() { return mNetworkId; } @@ -165,7 +179,11 @@ public final class CellIdentityCdma extends CellIdentity { /** * @return System Id 0..32767, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} * if unavailable. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public int getSystemId() { return mSystemId; } @@ -173,7 +191,10 @@ public final class CellIdentityCdma extends CellIdentity { /** * @return Base Station Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} * if unavailable. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public int getBasestationId() { return mBasestationId; } @@ -184,7 +205,11 @@ public final class CellIdentityCdma extends CellIdentity { * of 0.25 seconds and ranges from -2592000 to 2592000, both * values inclusive (corresponding to a range of -180 * to +180 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public int getLongitude() { return mLongitude; } @@ -195,7 +220,11 @@ public final class CellIdentityCdma extends CellIdentity { * of 0.25 seconds and ranges from -1296000 to 1296000, both * values inclusive (corresponding to a range of -90 * to +90 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public int getLatitude() { return mLatitude; } @@ -206,7 +235,11 @@ public final class CellIdentityCdma extends CellIdentity { super.hashCode()); } - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @NonNull @Override public CdmaCellLocation asCellLocation() { @@ -277,7 +310,13 @@ public final class CellIdentityCdma extends CellIdentity { if (DBG) log(toString()); } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + * + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SuppressWarnings("hiding") public static final @android.annotation.NonNull Creator<CellIdentityCdma> CREATOR = new Creator<CellIdentityCdma>() { diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java index aa8cff52bcaf..3c4bb5164ffc 100644 --- a/telephony/java/android/telephony/CellInfoCdma.java +++ b/telephony/java/android/telephony/CellInfoCdma.java @@ -16,17 +16,23 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; /** * A {@link CellInfo} representing a CDMA cell that provides identity and measurement info. + * + * @deprecated Legacy CDMA is unsupported. */ +@FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) +@Deprecated public final class CellInfoCdma extends CellInfo implements Parcelable { private static final String LOG_TAG = "CellInfoCdma"; @@ -61,7 +67,10 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { /** * @return a {@link CellIdentityCdma} instance. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public @NonNull CellIdentityCdma getCellIdentity() { return mCellIdentityCdma; @@ -75,7 +84,10 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { /** * @return a {@link CellSignalStrengthCdma} instance. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public @NonNull CellSignalStrengthCdma getCellSignalStrength() { return mCellSignalStrengthCdma; @@ -135,7 +147,12 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { return 0; } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags, TYPE_CDMA); @@ -154,7 +171,12 @@ public final class CellInfoCdma extends CellInfo implements Parcelable { if (DBG) log("CellInfoCdma(Parcel): " + toString()); } - /** Implement the Parcelable interface */ + /** + * Implement the Parcelable interface + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final @android.annotation.NonNull Creator<CellInfoCdma> CREATOR = new Creator<CellInfoCdma>() { @Override public CellInfoCdma createFromParcel(Parcel in) { diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java index d9437ab29881..2d650ab20802 100644 --- a/telephony/java/android/telephony/PreciseDisconnectCause.java +++ b/telephony/java/android/telephony/PreciseDisconnectCause.java @@ -255,25 +255,75 @@ public final class PreciseDisconnectCause { @FlaggedApi(Flags.FLAG_USE_OEM_DOMAIN_SELECTION_SERVICE) public static final int EMERGENCY_PERM_FAILURE = 326; - /** Mobile station (MS) is locked until next power cycle. */ + /** + * Mobile station (MS) is locked until next power cycle. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; - /** Drop call. */ + /** + * Drop call. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_DROP = 1001; - /** INTERCEPT order received, Mobile station (MS) state idle entered. */ + /** + * INTERCEPT order received, Mobile station (MS) state idle entered. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_INTERCEPT = 1002; - /** Mobile station (MS) has been redirected, call is cancelled. */ + /** + * Mobile station (MS) has been redirected, call is cancelled. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_REORDER = 1003; - /** Service option rejection. */ + /** + * Service option rejection. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_SO_REJECT = 1004; - /** Requested service is rejected, retry delay is set. */ + /** + * Requested service is rejected, retry delay is set. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_RETRY_ORDER = 1005; - /** Unable to obtain access to the CDMA system. */ + /** + * Unable to obtain access to the CDMA system. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ACCESS_FAILURE = 1006; - /** Not a preempted call. */ + /** + * Not a preempted call. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_PREEMPTED = 1007; - /** Not an emergency call. */ + /** + * Not an emergency call. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_NOT_EMERGENCY = 1008; - /** Access Blocked by CDMA network. */ + /** + * Access Blocked by CDMA network. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ACCESS_BLOCKED = 1009; /* OEM specific error codes. To be used by OEMs when they don't want to diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java index 90d6f89553b7..8b52f07102b8 100644 --- a/telephony/java/android/telephony/RadioAccessFamily.java +++ b/telephony/java/android/telephony/RadioAccessFamily.java @@ -66,6 +66,9 @@ public class RadioAccessFamily implements Parcelable { // 5G public static final int RAF_NR = (int) TelephonyManager.NETWORK_TYPE_BITMASK_NR; + /** NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */ + public static final int RAF_NB_IOT_NTN = (int) TelephonyManager.NETWORK_TYPE_BITMASK_NB_IOT_NTN; + // Grouping of RAFs // 2G private static final int GSM = RAF_GSM | RAF_GPRS | RAF_EDGE; @@ -80,6 +83,9 @@ public class RadioAccessFamily implements Parcelable { // 5G private static final int NR = RAF_NR; + /** Non-Terrestrial Network. */ + private static final int NB_IOT_NTN = RAF_NB_IOT_NTN; + /* Phone ID of phone */ private int mPhoneId; @@ -258,7 +264,7 @@ public class RadioAccessFamily implements Parcelable { raf = ((EVDO & raf) > 0) ? (EVDO | raf) : raf; raf = ((LTE & raf) > 0) ? (LTE | raf) : raf; raf = ((NR & raf) > 0) ? (NR | raf) : raf; - + raf = ((NB_IOT_NTN & raf) > 0) ? (NB_IOT_NTN | raf) : raf; return raf; } @@ -364,6 +370,7 @@ public class RadioAccessFamily implements Parcelable { case "WCDMA": return WCDMA; case "LTE_CA": return RAF_LTE_CA; case "NR": return RAF_NR; + case "NB_IOT_NTN": return RAF_NB_IOT_NTN; default: return RAF_UNKNOWN; } } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 127bbff01575..f8c3287fec36 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -233,6 +233,12 @@ public class ServiceState implements Parcelable { public static final int RIL_RADIO_TECHNOLOGY_NR = 20; /** + * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. + * @hide + */ + public static final int RIL_RADIO_TECHNOLOGY_NB_IOT_NTN = 21; + + /** * RIL Radio Annotation * @hide */ @@ -258,14 +264,16 @@ public class ServiceState implements Parcelable { ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA, ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA, - ServiceState.RIL_RADIO_TECHNOLOGY_NR}) + ServiceState.RIL_RADIO_TECHNOLOGY_NR, + ServiceState.RIL_RADIO_TECHNOLOGY_NB_IOT_NTN + }) public @interface RilRadioTechnology {} /** * The number of the radio technologies. */ - private static final int NEXT_RIL_RADIO_TECHNOLOGY = 21; + private static final int NEXT_RIL_RADIO_TECHNOLOGY = 22; /** @hide */ public static final int RIL_RADIO_CDMA_TECHNOLOGY_BITMASK = @@ -1125,6 +1133,9 @@ public class ServiceState implements Parcelable { case RIL_RADIO_TECHNOLOGY_NR: rtString = "NR_SA"; break; + case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN: + rtString = "NB_IOT_NTN"; + break; default: rtString = "Unexpected"; Rlog.w(LOG_TAG, "Unexpected radioTechnology=" + rt); @@ -1668,6 +1679,8 @@ public class ServiceState implements Parcelable { return TelephonyManager.NETWORK_TYPE_LTE_CA; case RIL_RADIO_TECHNOLOGY_NR: return TelephonyManager.NETWORK_TYPE_NR; + case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN: + return TelephonyManager.NETWORK_TYPE_NB_IOT_NTN; default: return TelephonyManager.NETWORK_TYPE_UNKNOWN; } @@ -1697,6 +1710,7 @@ public class ServiceState implements Parcelable { return AccessNetworkType.CDMA2000; case RIL_RADIO_TECHNOLOGY_LTE: case RIL_RADIO_TECHNOLOGY_LTE_CA: + case RIL_RADIO_TECHNOLOGY_NB_IOT_NTN: return AccessNetworkType.EUTRAN; case RIL_RADIO_TECHNOLOGY_NR: return AccessNetworkType.NGRAN; @@ -1757,6 +1771,8 @@ public class ServiceState implements Parcelable { return RIL_RADIO_TECHNOLOGY_LTE_CA; case TelephonyManager.NETWORK_TYPE_NR: return RIL_RADIO_TECHNOLOGY_NR; + case TelephonyManager.NETWORK_TYPE_NB_IOT_NTN: + return RIL_RADIO_TECHNOLOGY_NB_IOT_NTN; default: return RIL_RADIO_TECHNOLOGY_UNKNOWN; } @@ -1866,7 +1882,8 @@ public class ServiceState implements Parcelable { || radioTechnology == RIL_RADIO_TECHNOLOGY_TD_SCDMA || radioTechnology == RIL_RADIO_TECHNOLOGY_IWLAN || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA - || radioTechnology == RIL_RADIO_TECHNOLOGY_NR; + || radioTechnology == RIL_RADIO_TECHNOLOGY_NR + || radioTechnology == RIL_RADIO_TECHNOLOGY_NB_IOT_NTN; } @@ -1886,7 +1903,8 @@ public class ServiceState implements Parcelable { public static boolean isPsOnlyTech(int radioTechnology) { return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA - || radioTechnology == RIL_RADIO_TECHNOLOGY_NR; + || radioTechnology == RIL_RADIO_TECHNOLOGY_NR + || radioTechnology == RIL_RADIO_TECHNOLOGY_NB_IOT_NTN; } /** @hide */ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 65a52daae99f..453f8220c6c6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1397,25 +1397,44 @@ public class TelephonyManager { /** * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming * mode set to the radio default or to the user's preference if they've indicated one. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; /** * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits * connections on home networks. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ROAMING_MODE_HOME = 0; /** * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on * affiliated networks. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; /** * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on * any network. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_ROAMING_MODE_ANY = 2; - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = { CDMA_ROAMING_MODE_RADIO_DEFAULT, CDMA_ROAMING_MODE_HOME, @@ -1802,12 +1821,17 @@ public class TelephonyManager { * to indicate if the SIM combination in DSDS has limitation or compatible issue. * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE = "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE"; - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated @IntDef({ EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE, EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA @@ -1818,15 +1842,21 @@ public class TelephonyManager { /** * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} * to indicate there's no SIM combination warning. + * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0; /** * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} * to indicate two active SIMs are both CDMA hence there might be functional limitation. + * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1; /** @@ -1835,6 +1865,7 @@ public class TelephonyManager { * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios, and the * name will be "operator1 & operator2". * + * TODO(b/379356026): Deprecate if this is CDMA specific * @hide */ public static final String EXTRA_SIM_COMBINATION_NAMES = @@ -2414,9 +2445,12 @@ public class TelephonyManager { * higher, then a SecurityException is thrown.</li> * </ul> * + * @deprecated Legacy CDMA is unsupported. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236). @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -2456,9 +2490,12 @@ public class TelephonyManager { * * @param slotIndex of which MEID is returned * + * @deprecated Legacy CDMA is unsupported. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236). @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -2485,9 +2522,12 @@ public class TelephonyManager { * Returns the Manufacturer Code from the MEID. Return null if Manufacturer Code is not * available. * + * @deprecated Legacy CDMA is unsupported. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @Nullable public String getManufacturerCode() { @@ -2500,9 +2540,12 @@ public class TelephonyManager { * * @param slotIndex of which Type Allocation Code is returned * + * @deprecated Legacy CDMA is unsupported. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @Nullable public String getManufacturerCode(int slotIndex) { @@ -2648,7 +2691,13 @@ public class TelephonyManager { public static final int PHONE_TYPE_NONE = PhoneConstants.PHONE_TYPE_NONE; /** Phone radio is GSM. */ public static final int PHONE_TYPE_GSM = PhoneConstants.PHONE_TYPE_GSM; - /** Phone radio is CDMA. */ + /** + * Phone radio is CDMA. + * + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA; /** Phone is via SIP. */ public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP; @@ -3070,13 +3119,33 @@ public class TelephonyManager { public static final int NETWORK_TYPE_EDGE = TelephonyProtoEnums.NETWORK_TYPE_EDGE; // = 2. /** Current network is UMTS */ public static final int NETWORK_TYPE_UMTS = TelephonyProtoEnums.NETWORK_TYPE_UMTS; // = 3. - /** Current network is CDMA: Either IS95A or IS95B*/ + /** + * Current network is CDMA: Either IS95A or IS95B + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_CDMA = TelephonyProtoEnums.NETWORK_TYPE_CDMA; // = 4. - /** Current network is EVDO revision 0*/ + /** + * Current network is EVDO revision 0 + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_EVDO_0 = TelephonyProtoEnums.NETWORK_TYPE_EVDO_0; // = 5. - /** Current network is EVDO revision A*/ + /** + * Current network is EVDO revision A + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_EVDO_A = TelephonyProtoEnums.NETWORK_TYPE_EVDO_A; // = 6. - /** Current network is 1xRTT*/ + /** + * Current network is 1xRTT + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_1xRTT = TelephonyProtoEnums.NETWORK_TYPE_1XRTT; // = 7. /** Current network is HSDPA */ public static final int NETWORK_TYPE_HSDPA = TelephonyProtoEnums.NETWORK_TYPE_HSDPA; // = 8. @@ -3090,11 +3159,21 @@ public class TelephonyManager { */ @Deprecated public static final int NETWORK_TYPE_IDEN = TelephonyProtoEnums.NETWORK_TYPE_IDEN; // = 11. - /** Current network is EVDO revision B*/ + /** + * Current network is EVDO revision B + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_EVDO_B = TelephonyProtoEnums.NETWORK_TYPE_EVDO_B; // = 12. /** Current network is LTE */ public static final int NETWORK_TYPE_LTE = TelephonyProtoEnums.NETWORK_TYPE_LTE; // = 13. - /** Current network is eHRPD */ + /** + * Current network is eHRPD + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int NETWORK_TYPE_EHRPD = TelephonyProtoEnums.NETWORK_TYPE_EHRPD; // = 14. /** Current network is HSPA+ */ public static final int NETWORK_TYPE_HSPAP = TelephonyProtoEnums.NETWORK_TYPE_HSPAP; // = 15. @@ -3114,6 +3193,12 @@ public class TelephonyManager { * For 5G NSA, the network type will be {@link #NETWORK_TYPE_LTE}. */ public static final int NETWORK_TYPE_NR = TelephonyProtoEnums.NETWORK_TYPE_NR; // 20. + /** + * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. + */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) + public static final int NETWORK_TYPE_NB_IOT_NTN = + TelephonyProtoEnums.NETWORK_TYPE_NB_IOT_NTN; // 21 private static final @NetworkType int[] NETWORK_TYPES = { NETWORK_TYPE_GPRS, @@ -3190,6 +3275,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP * @see #NETWORK_TYPE_NR + * @see #NETWORK_TYPE_NB_IOT_NTN * * @hide */ @@ -3250,6 +3336,7 @@ public class TelephonyManager { * @see #NETWORK_TYPE_EHRPD * @see #NETWORK_TYPE_HSPAP * @see #NETWORK_TYPE_NR + * @see #NETWORK_TYPE_NB_IOT_NTN * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}. @@ -3400,6 +3487,8 @@ public class TelephonyManager { return "LTE_CA"; case NETWORK_TYPE_NR: return "NR"; + case NETWORK_TYPE_NB_IOT_NTN: + return "NB_IOT_NTN"; case NETWORK_TYPE_UNKNOWN: return "UNKNOWN"; default: @@ -3450,6 +3539,8 @@ public class TelephonyManager { return NETWORK_TYPE_BITMASK_LTE; case NETWORK_TYPE_NR: return NETWORK_TYPE_BITMASK_NR; + case NETWORK_TYPE_NB_IOT_NTN: + return NETWORK_TYPE_BITMASK_NB_IOT_NTN; case NETWORK_TYPE_IWLAN: return NETWORK_TYPE_BITMASK_IWLAN; case NETWORK_TYPE_IDEN: @@ -6761,9 +6852,13 @@ public class TelephonyManager { } } - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"ERI_"}, value = { + -1, ERI_ON, ERI_OFF, ERI_FLASH @@ -6773,24 +6868,37 @@ public class TelephonyManager { /** * ERI (Enhanced Roaming Indicator) is ON i.e value 0 defined by * 3GPP2 C.R1001-H v1.0 Table 8.1-1. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int ERI_ON = 0; /** * ERI (Enhanced Roaming Indicator) is OFF i.e value 1 defined by * 3GPP2 C.R1001-H v1.0 Table 8.1-1. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int ERI_OFF = 1; /** * ERI (Enhanced Roaming Indicator) is FLASH i.e value 2 defined by * 3GPP2 C.R1001-H v1.0 Table 8.1-1. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int ERI_FLASH = 2; - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"ERI_ICON_MODE_"}, value = { + -1, ERI_ICON_MODE_NORMAL, ERI_ICON_MODE_FLASH }) @@ -6802,7 +6910,9 @@ public class TelephonyManager { * * Note: ERI is defined 3GPP2 C.R1001-H Table 8.1-1 * @hide + * @deprecated Legacy CDMA is unsupported. */ + @Deprecated public static final int ERI_ICON_MODE_NORMAL = 0; /** @@ -6811,7 +6921,9 @@ public class TelephonyManager { * * Note: ERI is defined 3GPP2 C.R1001-H Table 8.1-1 * @hide + * @deprecated Legacy CDMA is unsupported. */ + @Deprecated public static final int ERI_ICON_MODE_FLASH = 1; /** @@ -6819,10 +6931,13 @@ public class TelephonyManager { * 3GPP2 C.R1001-H v1.0 Table 8.1-1. Additionally carriers define their own ERI display numbers. * Defined values are {@link #ERI_ON}, {@link #ERI_OFF}, and {@link #ERI_FLASH}. * + * @deprecated Legacy CDMA is unsupported. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -6832,8 +6947,10 @@ public class TelephonyManager { /** * Returns the CDMA ERI icon index to display for a subscription. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @UnsupportedAppUsage public @EriIconIndex int getCdmaEriIconIndex(int subId) { @@ -6856,8 +6973,10 @@ public class TelephonyManager { * 0 - ON * 1 - FLASHING * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @UnsupportedAppUsage public @EriIconMode int getCdmaEriIconMode(int subId) { @@ -6878,8 +6997,10 @@ public class TelephonyManager { /** * Returns the CDMA ERI text, * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getCdmaEriText() { return getCdmaEriText(getSubId()); @@ -6888,8 +7009,10 @@ public class TelephonyManager { /** * Returns the CDMA ERI text, of a subscription * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @UnsupportedAppUsage public String getCdmaEriText(int subId) { @@ -8166,8 +8289,10 @@ public class TelephonyManager { * @param itemID the ID of the item to read. * @return the NV item as a String, or null on any failure. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated @UnsupportedAppUsage public String nvReadItem(int itemID) { try { @@ -8194,8 +8319,10 @@ public class TelephonyManager { * @param itemValue the value to write, as a String. * @return true on success; false on any failure. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public boolean nvWriteItem(int itemID, String itemValue) { try { ITelephony telephony = getITelephony(); @@ -8220,8 +8347,10 @@ public class TelephonyManager { * @param preferredRoamingList byte array containing the new PRL. * @return true on success; false on any failure. * + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public boolean nvWriteCdmaPrl(byte[] preferredRoamingList) { try { ITelephony telephony = getITelephony(); @@ -8248,11 +8377,13 @@ public class TelephonyManager { * {@link #resetRadioConfig()} for reset type 3 (b/116476729) * * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset + * @deprecated NV APIs are deprecated starting from Android U. * @return true on success; false on any failure. * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Deprecated public boolean nvResetConfig(int resetType) { try { ITelephony telephony = getITelephony(); @@ -8283,10 +8414,13 @@ public class TelephonyManager { * * @return {@code true} on success; {@code false} on any failure. * + * @deprecated NV APIs are deprecated starting from Android U. * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}. * @hide */ + @Deprecated + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) @@ -8319,6 +8453,7 @@ public class TelephonyManager { * {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}. * @hide */ + @Deprecated @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) @@ -9272,20 +9407,26 @@ public class TelephonyManager { /** * Preferred network mode is CDMA and EvDo (auto mode, according to PRL). + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_CDMA_EVDO = RILConstants.NETWORK_MODE_CDMA; /** * Preferred network mode is CDMA only. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_CDMA_NO_EVDO = RILConstants.NETWORK_MODE_CDMA_NO_EVDO; /** * Preferred network mode is EvDo only. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_EVDO_NO_CDMA = RILConstants.NETWORK_MODE_EVDO_NO_CDMA; /** @@ -9296,8 +9437,10 @@ public class TelephonyManager { /** * Preferred network mode is LTE, CDMA and EvDo. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_LTE_CDMA_EVDO = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO; /** @@ -9308,8 +9451,10 @@ public class TelephonyManager { /** * Preferred network mode is LTE, CDMA, EvDo, GSM/WCDMA. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA; @@ -9379,14 +9524,18 @@ public class TelephonyManager { /** * Preferred network mode is TD-SCDMA,EvDo,CDMA,GSM/WCDMA. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; /** * Preferred network mode is TD-SCDMA/LTE/GSM/WCDMA, CDMA, and EvDo. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; @@ -9404,8 +9553,10 @@ public class TelephonyManager { /** * Preferred network mode is NR 5G, LTE, CDMA and EvDo. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_NR_LTE_CDMA_EVDO = RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO; @@ -9418,8 +9569,10 @@ public class TelephonyManager { /** * Preferred network mode is NR 5G, LTE, CDMA, EvDo, GSM and WCDMA. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA; @@ -9458,8 +9611,10 @@ public class TelephonyManager { /** * Preferred network mode is NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public static final int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; @@ -10160,6 +10315,9 @@ public class TelephonyManager { * This API will result in allowing an intersection of allowed network types for all reasons, * including the configuration done through other reasons. * + * If device supports satellite service, then + * {@link #NETWORK_TYPE_NB_IOT_NTN} is added to allowed network types for reason by default. + * * @param reason the reason the allowed network type change is taking place * @param allowedNetworkTypes The bitmask of allowed network type * @throws IllegalStateException if the Telephony process is not currently available. @@ -10209,6 +10367,10 @@ public class TelephonyManager { * <p>Requires permission: android.Manifest.READ_PRIVILEGED_PHONE_STATE or * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). * + * If device supports satellite service, then + * {@link #NETWORK_TYPE_NB_IOT_NTN} is added to allowed network types for reason by + * default. + * * @param reason the reason the allowed network type change is taking place * @return the allowed network type bitmask * @throws IllegalStateException if the Telephony process is not currently available. @@ -10275,7 +10437,7 @@ public class TelephonyManager { */ public static String convertNetworkTypeBitmaskToString( @NetworkTypeBitMask long networkTypeBitmask) { - String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NR) + String networkTypeName = IntStream.rangeClosed(NETWORK_TYPE_GPRS, NETWORK_TYPE_NB_IOT_NTN) .filter(x -> { return (networkTypeBitmask & getBitMaskForNetworkType(x)) == getBitMaskForNetworkType(x); @@ -10533,8 +10695,11 @@ public class TelephonyManager { /** * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -10545,8 +10710,11 @@ public class TelephonyManager { /** * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -10566,8 +10734,11 @@ public class TelephonyManager { /** * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -10578,8 +10749,11 @@ public class TelephonyManager { /** * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -11868,8 +12042,11 @@ public class TelephonyManager { * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -11912,8 +12089,11 @@ public class TelephonyManager { * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -11935,7 +12115,10 @@ public class TelephonyManager { } } - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated @IntDef(prefix = { "CDMA_SUBSCRIPTION_" }, value = { CDMA_SUBSCRIPTION_UNKNOWN, CDMA_SUBSCRIPTION_RUIM_SIM, @@ -11946,22 +12129,31 @@ public class TelephonyManager { /** * Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; /** * Used for CDMA subscription mode: RUIM/SIM (default) + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; /** * Used for CDMA subscription mode: NV -> non-volatile memory + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi public static final int CDMA_SUBSCRIPTION_NV = 1; @@ -11982,8 +12174,11 @@ public class TelephonyManager { * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -12022,8 +12217,11 @@ public class TelephonyManager { * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) @@ -13747,8 +13945,11 @@ public class TelephonyManager { * * @throws UnsupportedOperationException If the device does not have * {@link PackageManager#FEATURE_TELEPHONY_CDMA}. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA) public String getCdmaPrlVersion() { @@ -13762,8 +13963,10 @@ public class TelephonyManager { * * @param subId the subscription ID that this request applies to. * @return PRLVersion or null if error. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @Deprecated public String getCdmaPrlVersion(int subId) { try { ITelephony service = getITelephony(); @@ -14905,7 +15108,8 @@ public class TelephonyManager { NETWORK_TYPE_BITMASK_LTE_CA, NETWORK_TYPE_BITMASK_NR, NETWORK_TYPE_BITMASK_IWLAN, - NETWORK_TYPE_BITMASK_IDEN + NETWORK_TYPE_BITMASK_IDEN, + NETWORK_TYPE_BITMASK_NB_IOT_NTN }) public @interface NetworkTypeBitMask {} @@ -14928,7 +15132,10 @@ public class TelephonyManager { public static final long NETWORK_TYPE_BITMASK_EDGE = (1 << (NETWORK_TYPE_EDGE -1)); /** * network type bitmask indicating the support of radio tech CDMA(IS95A/IS95B). + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final long NETWORK_TYPE_BITMASK_CDMA = (1 << (NETWORK_TYPE_CDMA -1)); /** * network type bitmask indicating the support of radio tech 1xRTT. @@ -14950,7 +15157,10 @@ public class TelephonyManager { public static final long NETWORK_TYPE_BITMASK_EVDO_B = (1 << (NETWORK_TYPE_EVDO_B -1)); /** * network type bitmask indicating the support of radio tech EHRPD. + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final long NETWORK_TYPE_BITMASK_EHRPD = (1 << (NETWORK_TYPE_EHRPD -1)); /** * network type bitmask indicating the support of radio tech HSUPA. @@ -15006,6 +15216,12 @@ public class TelephonyManager { */ public static final long NETWORK_TYPE_BITMASK_IWLAN = (1 << (NETWORK_TYPE_IWLAN -1)); + /** + * network type bitmask indicating the support of readio tech NB IOT NTN. + */ + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) + public static final long NETWORK_TYPE_BITMASK_NB_IOT_NTN = (1 << (NETWORK_TYPE_NB_IOT_NTN - 1)); + /** @hide */ public static final long NETWORK_CLASS_BITMASK_2G = NETWORK_TYPE_BITMASK_GSM | NETWORK_TYPE_BITMASK_GPRS @@ -15034,6 +15250,9 @@ public class TelephonyManager { public static final long NETWORK_CLASS_BITMASK_5G = NETWORK_TYPE_BITMASK_NR; /** @hide */ + public static final long NETWORK_CLASS_BITMASK_NTN = NETWORK_TYPE_BITMASK_NB_IOT_NTN; + + /** @hide */ public static final long NETWORK_STANDARDS_FAMILY_BITMASK_3GPP = NETWORK_TYPE_BITMASK_GSM | NETWORK_TYPE_BITMASK_GPRS | NETWORK_TYPE_BITMASK_EDGE @@ -15045,9 +15264,13 @@ public class TelephonyManager { | NETWORK_TYPE_BITMASK_TD_SCDMA | NETWORK_TYPE_BITMASK_LTE | NETWORK_TYPE_BITMASK_LTE_CA - | NETWORK_TYPE_BITMASK_NR; + | NETWORK_TYPE_BITMASK_NR + | NETWORK_TYPE_BITMASK_NB_IOT_NTN; - /** @hide */ + /** @hide + * @deprecated Legacy CDMA is unsupported. + */ + @Deprecated public static final long NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2 = NETWORK_TYPE_BITMASK_CDMA | NETWORK_TYPE_BITMASK_1xRTT | NETWORK_TYPE_BITMASK_EVDO_0 @@ -18083,7 +18306,7 @@ public class TelephonyManager { */ public static boolean isNetworkTypeValid(@NetworkType int networkType) { return networkType >= TelephonyManager.NETWORK_TYPE_UNKNOWN && - networkType <= TelephonyManager.NETWORK_TYPE_NR; + networkType <= TelephonyManager.NETWORK_TYPE_NB_IOT_NTN; } /** diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java index 02429b5c2a2c..8fccf6505c0b 100644 --- a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java +++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java @@ -16,12 +16,15 @@ package android.telephony.cdma; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -34,18 +37,36 @@ import java.lang.annotation.RetentionPolicy; * containing an array of these objects to update its list of cell broadcast service categories * to display. * + * @deprecated Legacy CDMA is unsupported. * {@hide} */ +@FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) +@Deprecated @SystemApi public final class CdmaSmsCbProgramData implements Parcelable { - /** Delete the specified service category from the list of enabled categories. */ + /** + * Delete the specified service category from the list of enabled categories. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int OPERATION_DELETE_CATEGORY = 0; - /** Add the specified service category to the list of enabled categories. */ + /** + * Add the specified service category to the list of enabled categories. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int OPERATION_ADD_CATEGORY = 1; - /** Clear all service categories from the list of enabled categories. */ + /** + * Clear all service categories from the list of enabled categories. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int OPERATION_CLEAR_CATEGORIES = 2; /** @hide */ @@ -59,23 +80,53 @@ public final class CdmaSmsCbProgramData implements Parcelable { public @interface Operation {} // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1 - /** Indicates a presidential-level alert */ + /** + * Indicates a presidential-level alert + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000; - /** Indicates an extreme threat to life and property */ + /** + * Indicates an extreme threat to life and property + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_EXTREME_THREAT = 0x1001; - /** Indicates an severe threat to life and property */ + /** + * Indicates an severe threat to life and property + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_SEVERE_THREAT = 0x1002; - /** Indicates an AMBER child abduction emergency */ + /** + * Indicates an AMBER child abduction emergency + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003; - /** Indicates a CMAS test message */ + /** + * Indicates a CMAS test message + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_TEST_MESSAGE = 0x1004; - /** The last reserved value of a CMAS service category according to 3GPP C.R1001 table - * 9.3.3-1. */ + /** + * The last reserved value of a CMAS service category according to 3GPP C.R1001 table + * 9.3.3-1. + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff; /** @hide */ @@ -177,7 +228,10 @@ public final class CdmaSmsCbProgramData implements Parcelable { * * @param dest The Parcel in which the object should be written. * @param flags Additional flags about how the object should be written (ignored). + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mOperation); @@ -192,7 +246,10 @@ public final class CdmaSmsCbProgramData implements Parcelable { * Returns the service category operation, e.g. {@link #OPERATION_ADD_CATEGORY}. * * @return the service category operation + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public @Operation int getOperation() { return mOperation; } @@ -203,7 +260,10 @@ public final class CdmaSmsCbProgramData implements Parcelable { * 0x10FF are supported. * * @return a 16-bit CDMA service category value + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public @Category int getCategory() { return mCategory; } @@ -255,7 +315,11 @@ public final class CdmaSmsCbProgramData implements Parcelable { /** * Describe the kinds of special objects contained in the marshalled representation. * @return a bitmask indicating this Parcelable contains no special objects + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @Override public int describeContents() { return 0; @@ -263,7 +327,11 @@ public final class CdmaSmsCbProgramData implements Parcelable { /** * Creator for unparcelling objects. + * + * @deprecated Legacy CDMA is unsupported. */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @NonNull public static final Parcelable.Creator<CdmaSmsCbProgramData> CREATOR = new Parcelable.Creator<CdmaSmsCbProgramData>() { diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java index 8925a9e82942..76f83ee49dcd 100644 --- a/telephony/java/android/telephony/ims/RcsUceAdapter.java +++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java @@ -18,6 +18,7 @@ package android.telephony.ims; import android.Manifest; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +38,8 @@ import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; import android.util.Log; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -226,8 +229,11 @@ public class RcsUceAdapter { /** * A capability update has been requested due to moving to eHRPD. + * @deprecated Legacy CDMA is unsupported. * @hide */ + @FlaggedApi(Flags.FLAG_DEPRECATE_CDMA) + @Deprecated @SystemApi public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 4; diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java index 5e276aa49b05..0a1eedf097a4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java @@ -18,6 +18,7 @@ package android.telephony.satellite; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SystemApi; import com.android.internal.telephony.flags.Flags; @@ -26,13 +27,14 @@ import com.android.internal.telephony.flags.Flags; * * @hide */ -@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +@SystemApi +@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public interface SatelliteDisallowedReasonsCallback { /** * Called when disallowed reason of satellite has changed. * @param disallowedReasons Integer array of disallowed reasons. */ - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) - void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons); + void onSatelliteDisallowedReasonsChanged( + @NonNull @SatelliteManager.SatelliteDisallowedReason int[] disallowedReasons); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 1025c1506d4a..0f23f337d851 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -560,6 +560,8 @@ public final class SatelliteManager { * There is no valid satellite subscription selected. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public static final int SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION = 30; /** @hide */ @@ -2393,8 +2395,9 @@ public final class SatelliteManager { * * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public void requestSatelliteAccessConfigurationForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteAccessConfiguration, SatelliteException> callback) { @@ -2507,7 +2510,7 @@ public final class SatelliteManager { * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * If the request is successful, {@link OutcomeReceiver#onResult(Object)} - * will return the time after which the satellite will be visible. + * will return the selected NB IOT satellite subscription ID. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} * will return a {@link SatelliteException} with the {@link SatelliteResult}. * @@ -2515,6 +2518,8 @@ public final class SatelliteManager { * * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) public void requestSelectedNbIotSatelliteSubscriptionId( @NonNull @CallbackExecutor Executor executor, @@ -2574,6 +2579,8 @@ public final class SatelliteManager { * * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @SatelliteResult public int registerForSelectedNbIotSatelliteSubscriptionChanged( @NonNull @CallbackExecutor Executor executor, @@ -2619,6 +2626,8 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSelectedNbIotSatelliteSubscriptionChanged( @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) { @@ -2897,27 +2906,22 @@ public final class SatelliteManager { /** * Returns list of disallowed reasons of satellite. * - * @return list of disallowed reasons of satellite. + * @return Integer array of disallowed reasons. * * @throws SecurityException if caller doesn't have required permission. * @throws IllegalStateException if Telephony process isn't available. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) @SatelliteDisallowedReason - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) @NonNull - public List<Integer> getSatelliteDisallowedReasons() { + public int[] getSatelliteDisallowedReasons() { try { ITelephony telephony = getITelephony(); if (telephony != null) { - int[] receivedArray = telephony.getSatelliteDisallowedReasons(); - if (receivedArray.length == 0) { - logd("receivedArray is empty, create empty list"); - return new ArrayList<>(); - } else { - return Arrays.stream(receivedArray).boxed().collect(Collectors.toList()); - } + return telephony.getSatelliteDisallowedReasons(); } else { throw new IllegalStateException("Telephony service is null."); } @@ -2925,7 +2929,7 @@ public final class SatelliteManager { loge("getSatelliteDisallowedReasons() RemoteException: " + ex); ex.rethrowAsRuntimeException(); } - return new ArrayList<>(); + return new int[0]; } /** @@ -2938,8 +2942,9 @@ public final class SatelliteManager { * @throws IllegalStateException if Telephony process is not available. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public void registerForSatelliteDisallowedReasonsChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDisallowedReasonsCallback callback) { @@ -2981,8 +2986,9 @@ public final class SatelliteManager { * @throws IllegalStateException if Telephony process is not available. * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public void unregisterForSatelliteDisallowedReasonsChanged( @NonNull SatelliteDisallowedReasonsCallback callback) { Objects.requireNonNull(callback); @@ -3606,8 +3612,8 @@ public final class SatelliteManager { * @param executor The executor on which the callback will be called. * @param callback The callback object to which the result will be delivered. * If the request is successful, {@link OutcomeReceiver#onResult(Object)} - * will return display name of the satellite feature in string format. Defaults - * to satellite. If the request is not successful, + * will return display name of the satellite feature in string format. Default + * display name is "Satellite". If the request is not successful, * {@link OutcomeReceiver#onError(Throwable)} will return an error with * a SatelliteException. * @@ -3615,10 +3621,12 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. * @hide */ + @SystemApi + @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteDisplayName( @NonNull @CallbackExecutor Executor executor, - @NonNull OutcomeReceiver<String, SatelliteException> callback) { + @NonNull OutcomeReceiver<CharSequence, SatelliteException> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); @@ -3630,7 +3638,7 @@ public final class SatelliteManager { protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == SATELLITE_RESULT_SUCCESS) { if (resultData.containsKey(KEY_SATELLITE_DISPLAY_NAME)) { - String satelliteDisplayName = + CharSequence satelliteDisplayName = resultData.getString(KEY_SATELLITE_DISPLAY_NAME); executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onResult(satelliteDisplayName))); diff --git a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java index d8965547a20e..76caef3c9c7c 100644 --- a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java +++ b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java @@ -16,11 +16,18 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; +import android.annotation.SystemApi; + +import com.android.internal.telephony.flags.Flags; + /** * A callback class for selected satellite subscription changed events. * * @hide */ +@SystemApi +@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) public interface SelectedNbIotSatelliteSubscriptionCallback { /** * Called when the selected satellite subscription has changed. diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index da7669fd81ad..74d9204e9c84 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -416,6 +416,8 @@ interface ITelephony { * Returns the CDMA ERI icon index to display * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaEriIconIndex(String callingPackage, String callingFeatureId); @@ -424,6 +426,8 @@ interface ITelephony { * @param subId user preferred subId. * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaEriIconIndexForSubscriber(int subId, String callingPackage, String callingFeatureId); @@ -434,6 +438,8 @@ interface ITelephony { * 1 - FLASHING * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaEriIconMode(String callingPackage, String callingFeatureId); @@ -444,6 +450,8 @@ interface ITelephony { * @param subId user preferred subId. * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaEriIconModeForSubscriber(int subId, String callingPackage, String callingFeatureId); @@ -452,6 +460,8 @@ interface ITelephony { * Returns the CDMA ERI text, * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ String getCdmaEriText(String callingPackage, String callingFeatureId); @@ -460,6 +470,8 @@ interface ITelephony { * @param subId user preferred subId. * @param callingPackage package making the call. * @param callingFeatureId The feature in the package. + * + * @deprecated Legacy CDMA is unsupported. */ String getCdmaEriTextForSubscriber(int subId, String callingPackage, String callingFeatureId); @@ -779,6 +791,8 @@ interface ITelephony { * * @param itemID the ID of the item to read. * @return the NV item as a String, or null on any failure. + * + * @deprecated Legacy CDMA is unsupported. */ String nvReadItem(int itemID); @@ -789,6 +803,8 @@ interface ITelephony { * @param itemID the ID of the item to read. * @param itemValue the value to write, as a String. * @return true on success; false on any failure. + * + * @deprecated Legacy CDMA is unsupported. */ boolean nvWriteItem(int itemID, String itemValue); @@ -798,6 +814,8 @@ interface ITelephony { * * @param preferredRoamingList byte array containing the new PRL. * @return true on success; false on any failure. + * + * @deprecated Legacy CDMA is unsupported. */ boolean nvWriteCdmaPrl(in byte[] preferredRoamingList); @@ -811,6 +829,8 @@ interface ITelephony { * * @param slotIndex - device slot. * @return {@code true} on success; {@code false} on any failure. + * + * @deprecated Legacy CDMA is unsupported. */ boolean resetModemConfig(int slotIndex); @@ -1041,12 +1061,16 @@ interface ITelephony { /** * Return MDN string for CDMA phone. * @param subId user preferred subId. + * + * @deprecated Legacy CDMA is unsupported. */ String getCdmaMdn(int subId); /** * Return MIN string for CDMA phone. * @param subId user preferred subId. + * + * @deprecated Legacy CDMA is unsupported. */ String getCdmaMin(int subId); @@ -1495,13 +1519,14 @@ interface ITelephony { String getEsn(int subId); /** - * Return the Preferred Roaming List Version - * - * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission - * @param subId the subscription ID that this request applies to. - * @return PRLVersion or null if error. - * @hide - */ + * Return the Preferred Roaming List Version + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * @param subId the subscription ID that this request applies to. + * @return PRLVersion or null if error. + * @hide + * @deprecated Legacy CDMA is unsupported. + */ String getCdmaPrlVersion(int subId); /** @@ -1804,6 +1829,8 @@ interface ITelephony { * * @param the subscription id. * @return the roaming mode for CDMA phone. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaRoamingMode(int subId); @@ -1814,6 +1841,8 @@ interface ITelephony { * @param subId the subscription id. * @param mode the roaming mode should be set. * @return {@code true} if successed. + * + * @deprecated Legacy CDMA is unsupported. */ boolean setCdmaRoamingMode(int subId, int mode); @@ -1822,6 +1851,8 @@ interface ITelephony { * * @param the subscription id. * @return the subscription mode for CDMA phone. + * + * @deprecated Legacy CDMA is unsupported. */ int getCdmaSubscriptionMode(int subId); @@ -1832,6 +1863,8 @@ interface ITelephony { * @param subId the subscription id. * @param mode the subscription mode should be set. * @return {@code true} if successed. + * + * @deprecated Legacy CDMA is unsupported. */ boolean setCdmaSubscriptionMode(int subId, int mode); |