Merge "Updating Activity#requestOpenInBrowserEducation javadoc" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index da15c26..45e33ce 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -636,6 +636,11 @@
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 @@
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",
@@ -999,6 +989,11 @@
java_aconfig_library {
name: "android.app.flags-aconfig-java",
aconfig_declarations: "android.app.flags-aconfig",
+ min_sdk_version: "34",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -1472,6 +1467,13 @@
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 a525583b8..529da53 100644
--- a/Android.bp
+++ b/Android.bp
@@ -446,6 +446,9 @@
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 @@
apex_available: ["//apex_available:platform"],
visibility: [
"//frameworks/base:__subpackages__",
+ "//packages/modules/NeuralNetworks:__subpackages__",
],
compile_dex: false,
headers_only: true,
@@ -584,6 +588,9 @@
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 a5a08fb..fe80d1b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1981,7 +1981,12 @@
jobStatus.getNumAppliedFlexibleConstraints(),
jobStatus.getNumDroppedFlexibleConstraints(),
jobStatus.getFilteredTraceTag(),
- jobStatus.getFilteredDebugTags());
+ jobStatus.getFilteredDebugTags(),
+ jobStatus.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ jobStatus.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
+
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2422,7 +2427,11 @@
cancelled.getNumAppliedFlexibleConstraints(),
cancelled.getNumDroppedFlexibleConstraints(),
cancelled.getFilteredTraceTag(),
- cancelled.getFilteredDebugTags());
+ cancelled.getFilteredDebugTags(),
+ cancelled.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ cancelled.getJob().getBackoffPolicy() + 1,
+ shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 909a9b3..2b401c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -546,7 +546,11 @@
job.getNumAppliedFlexibleConstraints(),
job.getNumDroppedFlexibleConstraints(),
job.getFilteredTraceTag(),
- job.getFilteredDebugTags());
+ job.getFilteredDebugTags(),
+ job.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ job.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1681,7 +1685,11 @@
completedJob.getNumAppliedFlexibleConstraints(),
completedJob.getNumDroppedFlexibleConstraints(),
completedJob.getFilteredTraceTag(),
- completedJob.getFilteredDebugTags());
+ completedJob.getFilteredDebugTags(),
+ completedJob.getNumAbandonedFailures(),
+ /* 0 is reserved for UNKNOWN_POLICY */
+ completedJob.getJob().getBackoffPolicy() + 1,
+ mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/api/Android.bp b/api/Android.bp
index 7326203..14c2766 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -105,6 +105,13 @@
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 @@
"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 @@
"//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 5ca24de..e4d783e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -29,6 +29,7 @@
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 @@
// 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 6eead42..eaa984a 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -31,6 +31,7 @@
"car_bootclasspath_fragment",
"nfc_apex_bootclasspath_fragment",
"release_crashrecovery_module",
+ "release_ondevice_intelligence_module",
"release_package_profiling_module",
],
properties: [
@@ -176,6 +177,15 @@
},
],
},
+ 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 0fe8717..30323c0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -2201,6 +2201,11 @@
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardFastSpatialDamping;
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowEffectDamping;
field @FlaggedApi("android.os.material_motion_tokens") public static final int config_motionStandardSlowSpatialDamping;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusLarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusMedium;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusSmall;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXlarge;
+ field @FlaggedApi("android.os.material_shape_tokens") public static final int config_shapeCornerRadiusXsmall;
field public static final int dialog_min_width_major = 17104899; // 0x1050003
field public static final int dialog_min_width_minor = 17104900; // 0x1050004
field public static final int notification_large_icon_height = 17104902; // 0x1050006
@@ -6477,6 +6482,7 @@
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
@@ -8878,6 +8884,7 @@
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
@@ -24921,7 +24928,7 @@
method @Nullable public android.net.Uri getIconUri();
method @NonNull public String getId();
method @NonNull public CharSequence getName();
- method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.Set<java.lang.String> getRequiredPermissions();
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public java.util.List<java.util.Set<java.lang.String>> getRequiredPermissions();
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") public int getSupportedRoutingTypes();
method public int getType();
@@ -24991,6 +24998,7 @@
method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.Set<java.lang.String>);
+ method @FlaggedApi("com.android.media.flags.enable_route_visibility_control_api") @NonNull public android.media.MediaRoute2Info.Builder setRequiredPermissions(@NonNull java.util.List<java.util.Set<java.lang.String>>);
method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
method @FlaggedApi("com.android.media.flags.enable_mirroring_in_media_router_2") @NonNull public android.media.MediaRoute2Info.Builder setSupportedRoutingTypes(int);
method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
@@ -27162,6 +27170,15 @@
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();
@@ -27213,8 +27230,30 @@
}
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";
}
@@ -27226,13 +27265,14 @@
}
@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();
@@ -27240,6 +27280,7 @@
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);
@@ -27251,6 +27292,10 @@
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);
@@ -27258,7 +27303,7 @@
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);
@@ -27267,7 +27312,7 @@
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);
@@ -34721,6 +34766,8 @@
method public boolean hasMessages(android.os.Handler, Object, int);
method public boolean hasMessages(android.os.Handler, Object, Runnable);
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();
}
@@ -41000,7 +41047,7 @@
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
@@ -44992,6 +45039,7 @@
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";
@@ -45005,6 +45053,7 @@
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";
@@ -45016,6 +45065,8 @@
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";
@@ -47411,6 +47462,7 @@
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
@@ -47430,6 +47482,7 @@
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
@@ -55198,8 +55251,8 @@
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 ad5bd31..e71dffa 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 @@
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 8253b52..4a4776d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -328,6 +328,7 @@
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";
@@ -2276,149 +2277,6 @@
}
-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 {
@@ -5266,7 +5124,7 @@
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
- method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
+ method @Nullable public String getServiceDescriptor();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
}
@@ -5319,7 +5177,7 @@
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
- method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
+ method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable String);
method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
}
@@ -6325,7 +6183,7 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int, @NonNull android.hardware.location.NanoApp);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> loadNanoApp(@NonNull android.hardware.location.ContextHubInfo, @NonNull android.hardware.location.NanoAppBinary);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo);
- method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull android.hardware.contexthub.HubServiceInfo);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void openSession(@NonNull android.hardware.contexthub.HubEndpoint, @NonNull android.hardware.contexthub.HubEndpointInfo, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.util.List<android.hardware.location.NanoAppState>> queryNanoApps(@NonNull android.hardware.location.ContextHubInfo);
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
@@ -8129,12 +7987,13 @@
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);
@@ -12616,6 +12475,7 @@
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
field public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
+ field @FlaggedApi("com.android.internal.telephony.flags.action_sim_preference_settings") public static final String ACTION_SIM_PREFERENCE_SETTINGS = "android.settings.SIM_PREFERENCE_SETTINGS";
field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
@@ -13790,39 +13650,6 @@
}
-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 {
@@ -18824,6 +18651,10 @@
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();
@@ -18839,6 +18670,7 @@
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>);
@@ -18849,6 +18681,8 @@
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>);
@@ -18862,7 +18696,10 @@
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);
@@ -18874,6 +18711,8 @@
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";
@@ -18945,6 +18784,7 @@
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
@@ -19047,6 +18887,10 @@
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 0a806c7..6230a59 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3247,14 +3247,6 @@
}
-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 9140bdf..349b4ed 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/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index eccb6ff..abdfb535 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -44,6 +44,8 @@
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 @@
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 3cc5ff0..48b74f2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -165,6 +165,10 @@
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 @@
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(
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 06d01ec..063501b 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -46,6 +46,8 @@
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 @@
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 a4d8a5c..7e998d9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3221,7 +3221,6 @@
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean containsCustomViews() {
return contentView != null
|| bigContentView != null
@@ -3235,7 +3234,6 @@
/**
* @hide
*/
- @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean hasTitle() {
return extras != null
&& (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
@@ -3245,7 +3243,7 @@
/**
* @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 @@
}
/**
- * @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 @@
/**
* @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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f357836e..248e0433 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -42,8 +42,7 @@
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 @@
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 @@
ConnectivityFrameworkInitializerTiramisu.registerServiceWrappers();
NearbyFrameworkInitializer.registerServiceWrappers();
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
+ OnDeviceIntelligenceFrameworkInitializer.registerServiceWrappers();
DeviceLockFrameworkInitializer.registerServiceWrappers();
VirtualizationFrameworkInitializer.registerServiceWrappers();
ConnectivityFrameworkInitializerBaklava.registerServiceWrappers();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 01cc9d8..76705dc 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -308,12 +308,18 @@
public boolean isSleeping;
/**
- * Whether the top activity fillsParent() is false
+ * Whether the top activity fillsParent() is false.
* @hide
*/
public boolean isTopActivityTransparent;
/**
+ * Whether fillsParent() is false for every activity in the tasks stack.
+ * @hide
+ */
+ public boolean isActivityStackTransparent;
+
+ /**
* The last non-fullscreen bounds the task was launched in or resized to.
* @hide
*/
@@ -489,6 +495,7 @@
&& parentTaskId == that.parentTaskId
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
+ && isActivityStackTransparent == that.isActivityStackTransparent
&& Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
@@ -567,6 +574,7 @@
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
+ isActivityStackTransparent = source.readBoolean();
lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
@@ -623,6 +631,7 @@
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
+ dest.writeBoolean(isActivityStackTransparent);
dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
@@ -668,6 +677,7 @@
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ + " isActivityStackTransparent=" + isActivityStackTransparent
+ " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 21ec585..720e045 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -53,16 +53,6 @@
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"
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 361ba73..af035cb 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -343,16 +343,6 @@
}
flag {
- name: "dont_write_policy_definition"
- namespace: "enterprise"
- description: "Don't write redundant policy-definition-entry tags"
- bug: "335663055"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "active_admin_cleanup"
namespace: "enterprise"
description: "Remove ActiveAdmin from EnforcingAdmin and related cleanups"
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
index c8d80d3..d8179c7 100644
--- a/core/java/android/app/appfunctions/AppFunctionException.java
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -32,8 +32,8 @@
/**
* 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 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 @@
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 529b59a..e8cfd79 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -17,14 +17,14 @@
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/notification.aconfig b/core/java/android/app/notification.aconfig
index 8b6840c..7543fa9 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -284,6 +284,13 @@
}
flag {
+ name: "nm_binder_perf_cache_channels"
+ namespace: "systemui"
+ description: "Use IpcDataCache for notification channel/group lookups"
+ bug: "362981561"
+}
+
+flag {
name: "no_sbnholder"
namespace: "systemui"
description: "removes sbnholder from NLS"
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl b/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
deleted file mode 100644
index 6d70fc4..0000000
--- a/core/java/android/app/ondeviceintelligence/InferenceInfo.aidl
+++ /dev/null
@@ -1,22 +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.app.ondeviceintelligence;
-
-/**
- * @hide
- */
-parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS
deleted file mode 100644
index 85e9e65..0000000
--- 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 74a96c8..0000000
--- 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/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 82c7617..238f1cb 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -40,7 +40,7 @@
name: "pic_separate_permission_notifications"
is_fixed_read_only: true
description: "Seperate PermissionManager notifications from cache udpates"
- bug: "373752556"
+ bug: "379699402"
}
flag {
diff --git a/core/java/android/app/supervision/SupervisionManagerInternal.java b/core/java/android/app/supervision/SupervisionManagerInternal.java
index d571e14..2cf6ae6 100644
--- a/core/java/android/app/supervision/SupervisionManagerInternal.java
+++ b/core/java/android/app/supervision/SupervisionManagerInternal.java
@@ -27,32 +27,41 @@
*/
public abstract class SupervisionManagerInternal {
/**
- * Returns whether supervision is enabled for the specified user
+ * Returns whether the app with given process uid is the active supervision app.
*
- * @param userId The user to retrieve the supervision state for
- * @return whether the user is supervised
+ * <p>Supervision app is considered active when supervision is enabled for the user running the
+ * given process uid.
+ *
+ * @param uid App process uid.
+ * @return Whether the app is the active supervision app.
+ */
+ public abstract boolean isActiveSupervisionApp(int uid);
+
+ /**
+ * Returns whether supervision is enabled for the specified user.
+ *
+ * @param userId The user to retrieve the supervision state for.
+ * @return Whether the user is supervised.
*/
public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId);
- /**
- * Returns whether the supervision lock screen needs to be shown.
- */
+ /** Returns whether the supervision lock screen needs to be shown. */
public abstract boolean isSupervisionLockscreenEnabledForUser(@UserIdInt int userId);
/**
* Set whether supervision is enabled for the specified user.
*
- * @param userId The user to set the supervision state for
- * @param enabled Whether or not the user should be supervised
+ * @param userId The user to set the supervision state for.
+ * @param enabled Whether or not the user should be supervised.
*/
public abstract void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled);
/**
- * Sets whether the supervision lock screen should be shown for the specified user
+ * Sets whether the supervision lock screen should be shown for the specified user.
*
- * @param userId The user set the superivision state for
- * @param enabled Whether or not the superivision lock screen needs to be shown
- * @param options Optional configuration parameters for the supervision lock screen
+ * @param userId The user set the superivision state for.
+ * @param enabled Whether or not the superivision lock screen needs to be shown.
+ * @param options Optional configuration parameters for the supervision lock screen.
*/
public abstract void setSupervisionLockscreenEnabledForUser(
@UserIdInt int userId, boolean enabled, @Nullable PersistableBundle options);
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index d4f82f6..1b03532 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -24,3 +24,11 @@
description: "Flag that enables supervision when the supervision app is the profile owner"
bug: "377261590"
}
+
+flag {
+ name: "deprecate_dpm_supervision_apis"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag that deprecates supervision methods in DPM"
+ bug: "382034839"
+}
diff --git a/core/java/android/companion/DeviceId.java b/core/java/android/companion/DeviceId.java
index f66a1ae..d9514a0 100644
--- a/core/java/android/companion/DeviceId.java
+++ b/core/java/android/companion/DeviceId.java
@@ -154,6 +154,10 @@
/**
* 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> {
private String mCustomId;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 18d8c3a..a6492d3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,6 +55,7 @@
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.net.Uri;
+import android.os.BadParcelableException;
import android.os.Build;
import android.os.Bundle;
import android.os.BundleMerger;
@@ -12402,8 +12403,19 @@
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
if (mExtras != null && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
- Object value = mExtras.get(key);
-
+ Object value;
+ try {
+ value = mExtras.get(key);
+ } catch (BadParcelableException e) {
+ // This could happen when the key points to a LazyValue whose class cannot be
+ // found by the classLoader - A nested object more than 1 level deeper who is
+ // of type of a custom class could trigger this situation. In such case, we
+ // ignore it since it is not an intent. However, it could be a custom type that
+ // extends from Intent. If such an object is retrieved later in another
+ // component, then trying to launch such a custom class object will fail unless
+ // removeLaunchSecurityProtection() is called before it is launched.
+ value = null;
+ }
if (value instanceof Intent intent && !visited.contains(intent)) {
handleNestedIntent(intent, visited, new NestedIntentKey(
NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 44f2a4c..23f1ff8 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 68f17e2..d8d4e16 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -114,3 +114,13 @@
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/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 7efdd6d..1f12bbf 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,7 +107,7 @@
public void onSessionOpenRequest(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo)
+ @Nullable String serviceDescriptor)
throws RemoteException {
HubEndpointSession activeSession;
synchronized (mLock) {
@@ -128,16 +128,16 @@
processSessionOpenRequestResult(
sessionId,
initiator,
- serviceInfo,
+ serviceDescriptor,
mLifecycleCallback.onSessionOpenRequest(
- initiator, serviceInfo)));
+ initiator, serviceDescriptor)));
}
}
private void processSessionOpenRequestResult(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo,
+ @Nullable String serviceDescriptor,
HubEndpointSessionResult result) {
if (result == null) {
throw new IllegalArgumentException(
@@ -145,7 +145,7 @@
}
if (result.isAccepted()) {
- acceptSession(sessionId, initiator, serviceInfo);
+ acceptSession(sessionId, initiator, serviceDescriptor);
} else {
Log.i(
TAG,
@@ -162,7 +162,7 @@
private void acceptSession(
int sessionId,
HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
return;
@@ -187,7 +187,7 @@
HubEndpoint.this,
mAssignedHubEndpointInfo,
initiator,
- serviceInfo);
+ serviceDescriptor);
try {
// oneway call to notify system service that the request is completed
mServiceToken.openSessionRequestComplete(sessionId);
@@ -334,7 +334,6 @@
@Nullable IHubEndpointMessageCallback endpointMessageCallback,
@NonNull Executor messageCallbackExecutor) {
mPendingHubEndpointInfo = pendingEndpointInfo;
-
mLifecycleCallback = endpointLifecycleCallback;
mLifecycleCallbackExecutor = lifecycleCallbackExecutor;
mMessageCallback = endpointMessageCallback;
@@ -387,7 +386,7 @@
}
/** @hide */
- public void openSession(HubEndpointInfo destinationInfo, @Nullable HubServiceInfo serviceInfo) {
+ public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
// TODO(b/378974199): Consider refactor these assertions
if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
// No longer registered?
@@ -397,7 +396,7 @@
HubEndpointSession newSession;
try {
// Request system service to assign session id.
- int sessionId = mServiceToken.openSession(destinationInfo, serviceInfo);
+ int sessionId = mServiceToken.openSession(destinationInfo, serviceDescriptor);
// Save the newly created session
synchronized (mLock) {
@@ -407,7 +406,7 @@
HubEndpoint.this,
destinationInfo,
mAssignedHubEndpointInfo,
- serviceInfo);
+ serviceDescriptor);
mActiveSessions.put(sessionId, newSession);
}
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index b3d65c1..77f937e 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -44,7 +44,7 @@
@NonNull private final HubEndpoint mHubEndpoint;
@NonNull private final HubEndpointInfo mInitiator;
@NonNull private final HubEndpointInfo mDestination;
- @Nullable private final HubServiceInfo mServiceInfo;
+ @Nullable private final String mServiceDescriptor;
private final AtomicBoolean mIsClosed = new AtomicBoolean(true);
@@ -54,12 +54,12 @@
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
@NonNull HubEndpointInfo initiator,
- @Nullable HubServiceInfo serviceInfo) {
+ @Nullable String serviceDescriptor) {
mId = id;
mHubEndpoint = hubEndpoint;
mDestination = destination;
mInitiator = initiator;
- mServiceInfo = serviceInfo;
+ mServiceDescriptor = serviceDescriptor;
}
/**
@@ -69,6 +69,8 @@
* @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)
@@ -131,8 +133,8 @@
}
/**
- * Get the {@link HubServiceInfo} associated with this session. Null value indicates that there
- * is no service associated to this session.
+ * Get the service descriptor associated with this session. Null value indicates that there is
+ * no service associated to this session.
*
* <p>For hub initiated sessions, the object was previously used in as an argument for open
* request in {@link IHubEndpointLifecycleCallback#onSessionOpenRequest}.
@@ -141,8 +143,8 @@
* android.hardware.location.ContextHubManager#openSession}
*/
@Nullable
- public HubServiceInfo getServiceInfo() {
- return mServiceInfo;
+ public String getServiceDescriptor() {
+ return mServiceDescriptor;
}
@Override
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 1c98b4b..b76b227 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -34,11 +34,12 @@
* Request system service to open a session with a specific destination.
*
* @param destination A valid HubEndpointInfo representing the destination.
+ * @param serviceDescriptor An optional descriptor of the service to scope this session to.
*
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
- int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
+ int openSession(in HubEndpointInfo destination, in @nullable String serviceDescriptor);
/**
* Request system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
index 1ae5fb9..63edda8 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointCallback.aidl
@@ -29,9 +29,9 @@
*
* @param sessionId An integer identifying the session, assigned by the initiator
* @param initiator HubEndpointInfo representing the requester
- * @param serviceInfo Nullable HubServiceInfo representing the service associated with this session
+ * @param serviceDescriptor Nullable string representing the service associated with this session
*/
- void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable HubServiceInfo serviceInfo);
+ void onSessionOpenRequest(int sessionId, in HubEndpointInfo initiator, in @nullable String serviceDescriptor);
/**
* Request from system service to close a specific session
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index fe449bb..698ed0a 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -34,12 +34,12 @@
* Called when an endpoint is requesting a session be opened with another endpoint.
*
* @param requester The {@link HubEndpointInfo} object representing the requester
- * @param serviceInfo The {@link HubServiceInfo} object representing the service associated with
- * this session. Null indicates that there is no service associated with this session.
+ * @param serviceDescriptor A string describing the service associated with this session. Null
+ * indicates that there is no service associated with this session.
*/
@NonNull
HubEndpointSessionResult onSessionOpenRequest(
- @NonNull HubEndpointInfo requester, @Nullable HubServiceInfo serviceInfo);
+ @NonNull HubEndpointInfo requester, @Nullable String serviceDescriptor);
/**
* Called when a communication session is opened and ready to be used.
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 54d0dd0..1f7d426 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -582,6 +582,15 @@
}
}
+ /** Returns the graph representation of the topology */
+ public DisplayTopologyGraph getGraph() {
+ // TODO(b/364907904): implement
+ return new DisplayTopologyGraph(mPrimaryDisplayId,
+ new DisplayTopologyGraph.DisplayNode[] { new DisplayTopologyGraph.DisplayNode(
+ mRoot == null ? Display.DEFAULT_DISPLAY : mRoot.mDisplayId,
+ new DisplayTopologyGraph.AdjacentDisplay[0])});
+ }
+
/**
* Tests whether two brightness float values are within a small enough tolerance
* of each other.
diff --git a/core/java/android/hardware/display/DisplayTopologyGraph.java b/core/java/android/hardware/display/DisplayTopologyGraph.java
new file mode 100644
index 0000000..938e6d1
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopologyGraph.java
@@ -0,0 +1,43 @@
+/*
+ * 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.hardware.display;
+
+/**
+ * Graph of the displays in {@link android.hardware.display.DisplayTopology} tree.
+ *
+ * @hide
+ */
+public record DisplayTopologyGraph(int primaryDisplayId, DisplayNode[] displayNodes) {
+ /**
+ * Display in the topology
+ */
+ public record DisplayNode(
+ int displayId,
+ AdjacentDisplay[] adjacentDisplays) {}
+
+ /**
+ * Edge to adjacent display
+ */
+ public record AdjacentDisplay(
+ // The logical Id of this adjacent display
+ int displayId,
+ // Side of the other display which touches this adjacent display.
+ @DisplayTopology.TreeNode.Position
+ int position,
+ // How many px this display is shifted along the touchingSide, can be negative.
+ float offsetPx) {}
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 3a74130..d9888ad 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -841,6 +841,7 @@
* @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 @@
* @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 @@
*
* @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)
@@ -1309,16 +1312,18 @@
* ContextHubManager#registerEndpoint(HubEndpoint)}.
* @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
* endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
- * @param serviceInfo {@link HubServiceInfo} object that describes the service associated with
- * this session. The information will be sent to the destination as part of open request.
+ * @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)
public void openSession(
@NonNull HubEndpoint hubEndpoint,
@NonNull HubEndpointInfo destination,
- @NonNull HubServiceInfo serviceInfo) {
- hubEndpoint.openSession(destination, serviceInfo);
+ @NonNull String serviceDescriptor) {
+ hubEndpoint.openSession(destination, serviceDescriptor);
}
/**
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 9b37533..9badbf8 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -299,9 +299,10 @@
if (event.hasNoModifiers()) {
return false;
}
- return event.hasModifiers(KeyEvent.META_CTRL_ON)
- || event.hasModifiers(KeyEvent.META_ALT_ON)
- || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION);
+ return event.isCtrlPressed()
+ || event.isAltPressed()
+ || event.isFunctionPressed()
+ || event.isMetaPressed();
}
private boolean needsVerification(KeyEvent event) {
diff --git a/core/java/android/net/EventLogTags.logtags b/core/java/android/net/EventLogTags.logtags
index d5ed014..32953c9 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/Build.java b/core/java/android/os/Build.java
index 8b6da7e..84ca5ed 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -403,7 +403,7 @@
* increase when the hardware manufacturer provides an OTA update.
* <p>
* This constant records the major version of Android. Use {@link
- * SDK_INT_FULL} if you need to consider the minor version of Android
+ * #SDK_INT_FULL} if you need to consider the minor version of Android
* as well.
* <p>
* Possible values are defined in {@link Build.VERSION_CODES}.
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 804e8fa..11b80ce 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -18,6 +18,8 @@
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.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 @@
}
}
+ @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 @@
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 @@
* @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 @@
* @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 @@
}
}
- @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 @@
/* 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 @@
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 @@
mMessageDirectlyQueued = false;
nativePollOnce(ptr, mNextPollTimeoutMillis);
- Message msg = nextMessage();
+ Message msg = nextMessage(false);
if (msg != null) {
msg.markInUse();
return msg;
@@ -1043,8 +1067,9 @@
}
@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,92 @@
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);
+ }
+ }
+
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 +1388,9 @@
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 +1422,9 @@
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 +1457,9 @@
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 +1637,9 @@
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 +1701,9 @@
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 +1763,9 @@
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 +1871,7 @@
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 +1883,10 @@
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 +2592,7 @@
* 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 +2627,7 @@
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 +2672,7 @@
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 c2a47d7..47778ed 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 @@
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 @@
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 @@
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 @@
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 @@
}
@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,47 @@
}
}
+ 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);
+ }
+
private StateNode getStateNode(StackNode node) {
if (node.isMessageNode()) {
return ((MessageNode) node).mBottomOfStack;
@@ -1058,7 +1126,7 @@
* 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 +1235,9 @@
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 +1256,9 @@
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 +1278,9 @@
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 +1300,9 @@
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 +1340,9 @@
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 +1360,9 @@
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 +1379,9 @@
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 +1399,8 @@
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 +1411,9 @@
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/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index cae82d0..f49acd1 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 @@
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,78 @@
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);
+ }
+
boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index bfcc5cc..8d35338 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -118,9 +118,10 @@
# Memory
per-file OomKillRecord.java = file:/MEMORY_OWNERS
-# MessageQueue
+# MessageQueue and related classes
per-file MessageQueue.java = mfasheh@google.com, shayba@google.com
per-file Message.java = mfasheh@google.com, shayba@google.com
+per-file TestLooperManager.java = mfasheh@google.com, shayba@google.com
# Stats
per-file IStatsBootstrapAtomService.aidl = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 6855d95..d7e7ff2 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -932,6 +932,12 @@
/**
* 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();
@@ -942,6 +948,12 @@
* 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
@@ -963,6 +975,12 @@
* <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,6 +1050,13 @@
*
* @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() {
@@ -1043,6 +1068,12 @@
* 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
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 8aec7eb..9085fe0 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -277,7 +277,8 @@
if (service != null) {
return service;
} else {
- return Binder.allowBlocking(getIServiceManager().checkService(name).getBinder());
+ return Binder.allowBlocking(
+ getIServiceManager().checkService(name).getServiceWithMetadata().service);
}
} catch (RemoteException e) {
Log.e(TAG, "error in checkService", e);
@@ -425,7 +426,8 @@
private static IBinder rawGetService(String name) throws RemoteException {
final long start = sStatLogger.getTime();
- final IBinder binder = getIServiceManager().getService2(name).getBinder();
+ final IBinder binder =
+ getIServiceManager().getService2(name).getServiceWithMetadata().service;
final int time = (int) sStatLogger.logDurationStat(Stats.GET_SERVICE, start);
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 5a9c878..49b696d 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -61,7 +61,7 @@
@UnsupportedAppUsage
public IBinder getService(String name) throws RemoteException {
// Same as checkService (old versions of servicemanager had both methods).
- return checkService(name).getBinder();
+ return checkService(name).getServiceWithMetadata().service;
}
public Service getService2(String name) throws RemoteException {
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index 4b16c1d..e673e17 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,36 @@
}
/**
- * 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();
+ }
+
+ /**
+ * 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/flags.aconfig b/core/java/android/os/flags.aconfig
index 6357baa..2a46738 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -233,6 +233,14 @@
}
flag {
+ name: "material_shape_tokens"
+ namespace: "systemui"
+ description: "Adding new Material Tokens for M3 Shape (corner radius) Spec"
+ bug: "324928718"
+ is_exported: true
+}
+
+flag {
name: "message_queue_tail_tracking"
namespace: "system_performance"
description: "track tail of message queue."
diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
index c45c51d..af56bfe 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 @@
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/app/ondeviceintelligence/FeatureDetails.aidl b/core/java/android/os/instrumentation/IOffsetCallback.aidl
similarity index 61%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to core/java/android/os/instrumentation/IOffsetCallback.aidl
index 0589bf8..a28c93f 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/core/java/android/os/instrumentation/IOffsetCallback.aidl
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -14,9 +14,15 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package android.os.instrumentation;
+
+import android.os.instrumentation.ExecutableMethodFileOffsets;
/**
- * @hide
- */
-parcelable FeatureDetails;
+ * 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 0000000..57fc44f
--- /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/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index e4a3c9f..25e8a4d 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -213,7 +213,10 @@
private int mPreferenceHeaderItemResId = 0;
private boolean mPreferenceHeaderRemoveEmptyIcon = false;
+ private boolean mIsBackCallbackRegistered = false;
private final OnBackInvokedCallback mOnBackInvokedCallback = this::onBackInvoked;
+ private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
+ this::updateBackCallbackRegistrationState;
/**
* The starting request code given out to preference framework.
@@ -706,6 +709,7 @@
}
}
updateBackCallbackRegistrationState();
+ getFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}
@Override
@@ -715,17 +719,25 @@
private void updateBackCallbackRegistrationState() {
if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)) return;
- if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
- && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
- getOnBackInvokedDispatcher()
- .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
- } else {
+ if ((mCurHeader != null && getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null
+ && mSinglePane) || getFragmentManager().getBackStackEntryCount() != 0) {
+ if (!mIsBackCallbackRegistered) {
+ getOnBackInvokedDispatcher()
+ .registerOnBackInvokedCallback(PRIORITY_DEFAULT, mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = true;
+ }
+ } else if (mIsBackCallbackRegistered) {
getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
+ mIsBackCallbackRegistered = false;
}
}
private void onBackInvoked() {
- if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
+ if (WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(this)
+ && getFragmentManager().getBackStackEntryCount() != 0) {
+ getFragmentManager().popBackStackImmediate();
+ } else if (mCurHeader != null && mSinglePane
+ && getFragmentManager().getBackStackEntryCount() == 0
&& getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
mCurHeader = null;
@@ -1012,6 +1024,7 @@
@Override
protected void onDestroy() {
+ getFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
mHandler.removeMessages(MSG_BUILD_HEADERS);
super.onDestroy();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ae9ffa..4acb631 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2469,6 +2469,25 @@
= "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
/**
+ * Activity Action: Show the settings for users to select their preferred SIM subscription
+ * when a new SIM subscription has become available.
+ * <p>
+ * This Activity will only launch successfully if the newly active subscription ID is set as the
+ * value of {@link EXTRA_SUB_ID} and the value corresponds with an active SIM subscription.
+ * <p>
+ * Input: {@link #EXTRA_SUB_ID}: the subscription ID of the newly active SIM subscription.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_ACTION_SIM_PREFERENCE_SETTINGS)
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SIM_PREFERENCE_SETTINGS =
+ "android.settings.SIM_PREFERENCE_SETTINGS";
+
+ /**
* Intent Extra: The value of {@link android.app.settings.SettingsEnums#EntryPointType} for
* settings metrics that logs the entry point about physical keyboard settings.
* <p>
@@ -10023,6 +10042,12 @@
"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/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 45cd0f0..a46049f 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -17,6 +17,7 @@
package android.security.net.config;
import android.util.ArraySet;
+
import java.security.cert.X509Certificate;
import java.util.Set;
@@ -24,16 +25,23 @@
public final class CertificatesEntryRef {
private final CertificateSource mSource;
private final boolean mOverridesPins;
+ private final boolean mDisableCT;
- public CertificatesEntryRef(CertificateSource source, boolean overridesPins) {
+ public CertificatesEntryRef(CertificateSource source, boolean overridesPins,
+ boolean disableCT) {
mSource = source;
mOverridesPins = overridesPins;
+ mDisableCT = disableCT;
}
boolean overridesPins() {
return mOverridesPins;
}
+ boolean disableCT() {
+ return mDisableCT;
+ }
+
public Set<TrustAnchor> getTrustAnchors() {
// TODO: cache this [but handle mutable sources]
Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
diff --git a/core/java/android/security/net/config/KeyStoreConfigSource.java b/core/java/android/security/net/config/KeyStoreConfigSource.java
index 8d4f098..a54d8d0 100644
--- a/core/java/android/security/net/config/KeyStoreConfigSource.java
+++ b/core/java/android/security/net/config/KeyStoreConfigSource.java
@@ -17,8 +17,8 @@
package android.security.net.config;
import android.util.Pair;
+
import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.util.Set;
/**
@@ -32,7 +32,7 @@
mConfig = new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
// Use the KeyStore and do not override pins (of which there are none).
- new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false))
+ new CertificatesEntryRef(new KeyStoreCertificateSource(ks), false, false))
.build();
}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 129ae63..410c68b 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -112,7 +112,6 @@
return mHstsEnforced;
}
- // TODO(b/28746284): add exceptions for user-added certificates and enterprise overrides.
public boolean isCertificateTransparencyVerificationRequired() {
return mCertificateTransparencyVerificationRequired;
}
@@ -192,20 +191,21 @@
* @hide
*/
public static Builder getDefaultBuilder(ApplicationInfo info) {
+ // System certificate store, does not bypass static pins, does not disable CT.
+ CertificatesEntryRef systemRef = new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false);
Builder builder = new Builder()
.setHstsEnforced(DEFAULT_HSTS_ENFORCED)
- // System certificate store, does not bypass static pins.
- .addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
+ .addCertificatesEntryRef(systemRef);
final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
&& !info.isInstantApp();
builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
// Applications targeting N and above must opt in into trusting the user added certificate
// store.
if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
- // User certificate store, does not bypass static pins.
+ // User certificate store, does not bypass static pins. CT is disabled.
builder.addCertificatesEntryRef(
- new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
+ new CertificatesEntryRef(UserCertificateSource.getInstance(), false, true));
}
return builder;
}
@@ -339,6 +339,16 @@
if (mCertificateTransparencyVerificationRequiredSet) {
return mCertificateTransparencyVerificationRequired;
}
+ // CT verification has not been set explicitly. Before deferring to
+ // the parent, check if any of the CertificatesEntryRef requires it
+ // to be disabled (i.e., user store or inline certificate).
+ if (hasCertificatesEntryRefs()) {
+ for (CertificatesEntryRef ref : getCertificatesEntryRefs()) {
+ if (ref.disableCT()) {
+ return false;
+ }
+ }
+ }
if (mParentBuilder != null) {
return mParentBuilder.getCertificateTransparencyVerificationRequired();
}
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index b1c1479..95e579f 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -182,6 +182,7 @@
boolean overridePins =
parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
int sourceId = parser.getAttributeResourceValue(null, "src", -1);
+ boolean disableCT = false;
String sourceString = parser.getAttributeValue(null, "src");
CertificateSource source = null;
if (sourceString == null) {
@@ -190,10 +191,12 @@
if (sourceId != -1) {
// TODO: Cache ResourceCertificateSources by sourceId
source = new ResourceCertificateSource(sourceId, mContext);
+ disableCT = true;
} else if ("system".equals(sourceString)) {
source = SystemCertificateSource.getInstance();
} else if ("user".equals(sourceString)) {
source = UserCertificateSource.getInstance();
+ disableCT = true;
} else if ("wfa".equals(sourceString)) {
source = WfaCertificateSource.getInstance();
} else {
@@ -201,7 +204,7 @@
+ "Should be one of system|user|@resourceVal");
}
XmlUtils.skipCurrentTag(parser);
- return new CertificatesEntryRef(source, overridePins);
+ return new CertificatesEntryRef(source, overridePins, disableCT);
}
private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser,
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 42dbd37..8add9f7 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -97,14 +97,6 @@
}
flag {
- name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected"
- namespace: "responsible_apis"
- description: "Prevent intent redirect attacks by showing a toast if not yet collected"
- bug: "361143368"
- is_fixed_read_only: true
-}
-
-flag {
name: "prevent_intent_redirect_show_toast_if_nested_keys_not_collected_r_w"
namespace: "responsible_apis"
description: "Prevent intent redirect attacks by showing a toast if not yet collected"
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index fba8e42..6d62a2c 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -341,7 +341,7 @@
/** 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 09774f7..0000000
--- 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 e209a28..5ba2bae 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 c9d560c..802bddd 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1301,17 +1301,23 @@
}
/**
- * 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 @@
*
* <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 6d85e75..5da4985 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -766,7 +766,7 @@
* container.
*/
@EnforcePermission("MANAGE_APP_TOKENS")
- void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes,
+ void updateDisplayWindowRequestedVisibleTypes(int displayId, int visibleTypes, int mask,
in @nullable ImeTracker.Token statsToken);
/**
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index b796e0b..ba20839 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -357,6 +357,31 @@
} else if (mTmpFrame.right == relativeFrame.right) {
return Insets.of(0, 0, mTmpFrame.width(), 0);
}
+ } else {
+ // The source doesn't cover the width or the height of relativeFrame, but just parts of
+ // them. Here uses mSideHint to decide which side should be inset.
+ switch (mSideHint) {
+ case SIDE_LEFT:
+ if (mTmpFrame.left == relativeFrame.left) {
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ }
+ break;
+ case SIDE_TOP:
+ if (mTmpFrame.top == relativeFrame.top) {
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ }
+ break;
+ case SIDE_RIGHT:
+ if (mTmpFrame.right == relativeFrame.right) {
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ }
+ break;
+ case SIDE_BOTTOM:
+ if (mTmpFrame.bottom == relativeFrame.bottom) {
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ break;
+ }
}
return Insets.NONE;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 049189f..0681745 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28277,14 +28277,28 @@
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
- if (mParent != null && !mParent.isLayoutRequested()) {
- mParent.requestLayout();
+ if (mParent != null) {
+ if (!mParent.isLayoutRequested()) {
+ mParent.requestLayout();
+ } else {
+ clearMeasureCacheOfAncestors();
+ }
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
+ private void clearMeasureCacheOfAncestors() {
+ ViewParent parent = mParent;
+ while (parent instanceof View view) {
+ if (view.mMeasureCache != null) {
+ view.mMeasureCache.clear();
+ }
+ parent = view.mParent;
+ }
+ }
+
/**
* Forces this view to be laid out during the next layout pass.
* This method does not call requestLayout() or forceLayout()
@@ -28640,8 +28654,10 @@
*/
@RemotableViewMethod
public void setMinimumHeight(int minHeight) {
- mMinHeight = minHeight;
- requestLayout();
+ if (mMinHeight != minHeight) {
+ mMinHeight = minHeight;
+ requestLayout();
+ }
}
/**
@@ -28671,8 +28687,10 @@
*/
@RemotableViewMethod
public void setMinimumWidth(int minWidth) {
- mMinWidth = minWidth;
- requestLayout();
+ if (mMinWidth != minWidth) {
+ mMinWidth = minWidth;
+ requestLayout();
+ }
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 63bf392..9e97a8e 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -591,7 +591,7 @@
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 1be7f48..43a946a 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -87,8 +87,12 @@
* <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 @@
* "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/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index df0c5a3..8a10979 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -6540,6 +6540,15 @@
* Class with information if a node is a range.
*/
public static final class RangeInfo {
+ /** @hide */
+ @IntDef(prefix = { "RANGE_TYPE_" }, value = {
+ RANGE_TYPE_INT,
+ RANGE_TYPE_FLOAT,
+ RANGE_TYPE_PERCENT,
+ RANGE_TYPE_INDETERMINATE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RangeType {}
/** Range type: integer. */
public static final int RANGE_TYPE_INT = 0;
@@ -6588,7 +6597,7 @@
* @param current The current value.
*/
@Deprecated
- public static RangeInfo obtain(int type, float min, float max, float current) {
+ public static RangeInfo obtain(@RangeType int type, float min, float max, float current) {
return new RangeInfo(type, min, max, current);
}
@@ -6602,7 +6611,7 @@
* maximum.
* @param current The current value.
*/
- public RangeInfo(int type, float min, float max, float current) {
+ public RangeInfo(@RangeType int type, float min, float max, float current) {
mType = type;
mMin = min;
mMax = max;
@@ -6618,6 +6627,7 @@
* @see #RANGE_TYPE_FLOAT
* @see #RANGE_TYPE_PERCENT
*/
+ @RangeType
public int getType() {
return mType;
}
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index ebda4d4..ddf6ff1 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 {
- 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 a90aebd..8bbd5a9 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 3c854ea..0721fd3 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 @@
atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
// TODO: b/360198915 - Add unit testing for using ScrollFeedbackProvider
- if (enableTouchScrollFeedback()) {
+ if (enableScrollFeedbackForTouch()) {
initHapticScrollFeedbackProviderIfNotExists();
mHapticScrollFeedbackProvider.onScrollProgress(
vtev.getDeviceId(), vtev.getSource(), MotionEvent.AXIS_Y,
@@ -3779,7 +3779,7 @@
mTouchMode = TOUCH_MODE_OVERSCROLL;
}
- if (enableTouchScrollFeedback()) {
+ if (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 511c832..184933f 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 @@
}
// TODO: b/360198915 - Add unit tests.
- if (enableTouchScrollFeedback()) {
+ if (enableScrollFeedbackForTouch()) {
if (hitTopLimit || hitBottomLimit) {
initHapticScrollFeedbackProviderIfNotExists();
mHapticScrollFeedbackProvider.onScrollLimit(vtev.getDeviceId(),
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index fe936f7..f42c0ec 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -44,10 +44,10 @@
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 @@
@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 @@
@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 @@
@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 @@
@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 @@
/** @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 @@
};
@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 @@
}
+ @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 @@
* @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 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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
};
@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/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 8ebc1ed..a04071a 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 {
+ 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 92f9e60..5d4c408 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -267,7 +267,9 @@
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/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
index 66ee131..dfec499 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/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
index fed8fe3..b20f6d2 100644
--- a/core/java/com/android/internal/policy/KeyInterceptionInfo.java
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -27,13 +27,11 @@
// Debug friendly name to help identify the window
public final String windowTitle;
public final int windowOwnerUid;
- public final int inputFeaturesFlags;
- public KeyInterceptionInfo(int type, int flags, String title, int uid, int inputFeaturesFlags) {
+ public KeyInterceptionInfo(int type, int flags, String title, int uid) {
layoutParamsType = type;
layoutParamsPrivateFlags = flags;
windowTitle = title;
windowOwnerUid = uid;
- this.inputFeaturesFlags = inputFeaturesFlags;
}
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ff08dd2..3e2f301 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -67,6 +67,13 @@
// ---- Methods below are for use by the status bar policy services ----
// You need the STATUS_BAR_SERVICE permission
RegisterStatusBarResult registerStatusBar(IStatusBar callbacks);
+ /**
+ * Registers the status bar for all displays.
+ *
+ * Returns a map of all display IDs (as strings) to their corresponding RegisterStatusBarResult
+ * objects.
+ */
+ Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar callbacks);
void onPanelRevealed(boolean clearNotificationEffects, int numItems);
void onPanelHidden();
// Mark current notifications as "seen" and stop ringing, vibrating, blinking.
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 1e965c5d..bda7547 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.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 @@
/**
* 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 @@
}
/**
+ * 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/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 754f77e7..d49afa7 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -435,9 +435,11 @@
public void startListeningForLatencyTrackerConfigChanges() {
final Context context = ActivityThread.currentApplication();
if (context == null) {
- if (DEBUG) {
- Log.d(TAG, "No application for package: " + ActivityThread.currentPackageName());
- }
+ Log.e(
+ TAG,
+ String.format(
+ "No application for package: %s. Latency Tracker Disabled",
+ ActivityThread.currentPackageName()));
return;
}
if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) {
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/LocalFloatingToolbarPopup.java
index 38685b6..5177a03 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 @@
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 da880e1..1e9ba78 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -697,6 +697,7 @@
}
}
}
+ op.markNotDirty();
op.apply(context);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 11e58ba..5c3df7e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -333,6 +333,7 @@
public void overrideColor(int id, int color) {
mColorOverride[id] = true;
mColorMap.put(id, color);
+ updateListeners(id);
}
/** Clear the color Overrides */
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 29124d0..bb112d1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -132,9 +132,6 @@
@Override
public void apply(@NonNull RemoteContext context) {
- if (context.getMode() != RemoteContext.ContextMode.DATA) {
- return;
- }
context.addClickArea(
mId, mContentDescription, mOutLeft, mOutTop, mOutRight, mOutBottom, mMetadata);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index 6c9105d..e9aae1e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -64,6 +64,7 @@
@Override
public void apply(@NonNull RemoteContext context) {
context.setTheme(mTheme);
+ markDirty();
}
@NonNull
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b7a7f96..8e3303a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -92,6 +92,7 @@
"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",
@@ -220,6 +221,7 @@
"android_hardware_camera2_utils_SurfaceUtils.cpp",
"android_hardware_display_DisplayManagerGlobal.cpp",
"android_hardware_display_DisplayViewport.cpp",
+ "android_hardware_display_DisplayTopology.cpp",
"android_hardware_HardwareBuffer.cpp",
"android_hardware_OverlayProperties.cpp",
"android_hardware_SensorManager.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ac187b0..78d69f0 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -220,6 +220,7 @@
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 @@
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/android_hardware_display_DisplayTopology.cpp b/core/jni/android_hardware_display_DisplayTopology.cpp
new file mode 100644
index 0000000..d9e802d
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "DisplayTopology-JNI"
+
+#include <android_hardware_display_DisplayTopology.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <utils/Errors.h>
+
+#include "jni_wrappers.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+ jclass clazz;
+ jfieldID primaryDisplayId;
+ jfieldID displayNodes;
+} gDisplayTopologyGraphClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID adjacentDisplays;
+} gDisplayTopologyGraphNodeClassInfo;
+
+static struct {
+ jclass clazz;
+ jfieldID displayId;
+ jfieldID position;
+ jfieldID offsetPx;
+} gDisplayTopologyGraphAdjacentDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+status_t android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(
+ JNIEnv* env, jobject adjacentDisplayObj, DisplayTopologyAdjacentDisplay* adjacentDisplay) {
+ adjacentDisplay->displayId = ui::LogicalDisplayId{
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId)};
+ adjacentDisplay->position = static_cast<DisplayTopologyPosition>(
+ env->GetIntField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position));
+ adjacentDisplay->offsetPx =
+ env->GetFloatField(adjacentDisplayObj,
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx);
+ return OK;
+}
+
+status_t android_hardware_display_DisplayTopologyGraphNode_toNative(
+ JNIEnv* env, jobject nodeObj,
+ std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>>&
+ graph) {
+ ui::LogicalDisplayId displayId = ui::LogicalDisplayId{
+ env->GetIntField(nodeObj, gDisplayTopologyGraphNodeClassInfo.displayId)};
+
+ jobjectArray adjacentDisplaysArray = static_cast<jobjectArray>(
+ env->GetObjectField(nodeObj, gDisplayTopologyGraphNodeClassInfo.adjacentDisplays));
+
+ if (adjacentDisplaysArray) {
+ jsize length = env->GetArrayLength(adjacentDisplaysArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject>
+ adjacentDisplayObj(env, env->GetObjectArrayElement(adjacentDisplaysArray, i));
+ if (NULL != adjacentDisplayObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ DisplayTopologyAdjacentDisplay adjacentDisplay;
+ android_hardware_display_DisplayTopologyAdjacentDisplay_toNative(env,
+ adjacentDisplayObj
+ .get(),
+ &adjacentDisplay);
+ graph[displayId].push_back(adjacentDisplay);
+ }
+ }
+ return OK;
+}
+
+DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(JNIEnv* env,
+ jobject topologyObj) {
+ DisplayTopologyGraph topology;
+ topology.primaryDisplayId = ui::LogicalDisplayId{
+ env->GetIntField(topologyObj, gDisplayTopologyGraphClassInfo.primaryDisplayId)};
+
+ jobjectArray nodesArray = static_cast<jobjectArray>(
+ env->GetObjectField(topologyObj, gDisplayTopologyGraphClassInfo.displayNodes));
+
+ if (nodesArray) {
+ jsize length = env->GetArrayLength(nodesArray);
+ for (jsize i = 0; i < length; i++) {
+ ScopedLocalRef<jobject> nodeObj(env, env->GetObjectArrayElement(nodesArray, i));
+ if (NULL != nodeObj.get()) {
+ break; // found null element indicating end of used portion of the array
+ }
+
+ android_hardware_display_DisplayTopologyGraphNode_toNative(env, nodeObj.get(),
+ topology.graph);
+ }
+ }
+ return topology;
+}
+
+// ----------------------------------------------------------------------------
+
+int register_android_hardware_display_DisplayTopology(JNIEnv* env) {
+ jclass graphClazz = FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph");
+ gDisplayTopologyGraphClassInfo.clazz = MakeGlobalRefOrDie(env, graphClazz);
+
+ gDisplayTopologyGraphClassInfo.primaryDisplayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "primaryDisplayId", "I");
+ gDisplayTopologyGraphClassInfo.displayNodes =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphClassInfo.clazz, "displayNodes",
+ "[Landroid/hardware/display/DisplayTopologyGraph$DisplayNode;");
+
+ jclass displayNodeClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$DisplayNode");
+ gDisplayTopologyGraphNodeClassInfo.clazz = MakeGlobalRefOrDie(env, displayNodeClazz);
+ gDisplayTopologyGraphNodeClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "displayId", "I");
+ gDisplayTopologyGraphNodeClassInfo.adjacentDisplays =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphNodeClassInfo.clazz, "adjacentDisplays",
+ "[Landroid/hardware/display/DisplayTopologyGraph$AdjacentDisplay;");
+
+ jclass adjacentDisplayClazz =
+ FindClassOrDie(env, "android/hardware/display/DisplayTopologyGraph$AdjacentDisplay");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz =
+ MakeGlobalRefOrDie(env, adjacentDisplayClazz);
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.displayId =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "displayId",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.position =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "position",
+ "I");
+ gDisplayTopologyGraphAdjacentDisplayClassInfo.offsetPx =
+ GetFieldIDOrDie(env, gDisplayTopologyGraphAdjacentDisplayClassInfo.clazz, "offsetPx",
+ "F");
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_hardware_display_DisplayTopology.h b/core/jni/android_hardware_display_DisplayTopology.h
new file mode 100644
index 0000000..390191f
--- /dev/null
+++ b/core/jni/android_hardware_display_DisplayTopology.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <input/DisplayTopologyGraph.h>
+
+#include "jni.h"
+
+namespace android {
+
+/**
+ * Copies the contents of a DVM DisplayTopology object to a new native DisplayTopology instance.
+ * Returns DisplayTopology.
+ */
+extern DisplayTopologyGraph android_hardware_display_DisplayTopologyGraph_toNative(
+ JNIEnv* env, jobject eventObj);
+
+} // namespace android
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index b2eeff3..f40cfd9f 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -532,7 +532,12 @@
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 0000000..c706258
--- /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 903d08b..be4fb8b 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -179,6 +179,7 @@
"art-aconfig-flags",
"ranging_aconfig_flags",
"aconfig_settingslib_flags",
+ "telephony_flags",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d0a5318..6b05690 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 09c02c9..76c810b 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 f539105..2e0a7af 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 ddf3ebc..f644ade 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 b7fe454..63872af 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-watch/config_material.xml b/core/res/res/values-watch/config_material.xml
index 8e9693a..73a7c09 100644
--- a/core/res/res/values-watch/config_material.xml
+++ b/core/res/res/values-watch/config_material.xml
@@ -69,4 +69,14 @@
<integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
<item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
<integer name="config_motionExpressiveSlowEffectStiffness">260</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/wear-m3/tokens/designSystems/70fbaa4f7722a3d1/tokenSets/4fa2518eaeaf65eb
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">18dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">26dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">36dp</dimen>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 596a9af..53b47622 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2372,7 +2372,7 @@
<string name="default_sms_application" translatable="false">com.android.messaging</string>
<!-- Flag indicating whether the current device supports "Ask every time" for sms-->
- <bool name="config_sms_ask_every_time_support">true</bool>
+ <bool name="config_sms_ask_every_time_support">false</bool>
<!-- Flag indicating whether the current device allows acknowledgement of SIM operation like
SM-PP or saving SMS to SIM can be done via the IMS interfaces.
@@ -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_material.xml b/core/res/res/values/config_material.xml
index 6034f9c..648fe90 100644
--- a/core/res/res/values/config_material.xml
+++ b/core/res/res/values/config_material.xml
@@ -75,4 +75,14 @@
<integer name="config_motionExpressiveSlowSpatialStiffness">200</integer>
<item name="config_motionExpressiveSlowEffectDamping" format="float" type="dimen">1.0</item>
<integer name="config_motionExpressiveSlowEffectStiffness">800</integer>
+
+ <!--
+ Material rounded corner configs
+ Values from https://carbon.googleplex.com/google-material-3/tokens/designSystems/20543ce18892f7d9/tokenSets/21c40db4e4f5af15
+ -->
+ <dimen name="config_shapeCornerRadiusXsmall">4dp</dimen>
+ <dimen name="config_shapeCornerRadiusSmall">8dp</dimen>
+ <dimen name="config_shapeCornerRadiusMedium">12dp</dimen>
+ <dimen name="config_shapeCornerRadiusLarge">16dp</dimen>
+ <dimen name="config_shapeCornerRadiusXlarge">28dp</dimen>
</resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index bb76b9f..196da29 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/public-staging.xml b/core/res/res/values/public-staging.xml
index 7b9d213..6c73b0c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -184,6 +184,16 @@
<public name="config_motionExpressiveSlowSpatialDamping"/>
<!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_MOTION_TOKENS)-->
<public name="config_motionExpressiveSlowEffectDamping"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXsmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusSmall"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusMedium"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusLarge"/>
+ <!-- @FlaggedApi(android.os.Flags.FLAG_MATERIAL_SHAPE_TOKENS)-->
+ <public name="config_shapeCornerRadiusXlarge"/>
</staging-public-group>
<staging-public-group type="color" first-id="0x01b20000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2671ff9..28de553 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" />
@@ -5878,4 +5877,12 @@
<!-- List of protected packages that require biometric authentication for modification -->
<java-symbol type="array" name="config_biometric_protected_package_names" />
+ <!-- Material shape spec config tokens -->
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXsmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusSmall"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusMedium"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusLarge"/>
+ <java-symbol type="dimen" name="config_shapeCornerRadiusXlarge"/>
+
+
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 63e678d..9effeec 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -467,12 +467,25 @@
.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 @@
.setContentTitle("TITLE")
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isFalse();
}
@@ -491,6 +505,7 @@
.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 @@
.setStyle(new Notification.BigTextStyle())
.setColor(Color.WHITE)
.setColorized(true)
+ .setOngoing(true)
.build();
assertThat(n.hasPromotableCharacteristics()).isFalse();
}
@@ -515,6 +531,7 @@
.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/QueuedWorkTest.java b/core/tests/coretests/src/android/app/QueuedWorkTest.java
index 230c9e8..6bd9b6a 100644
--- a/core/tests/coretests/src/android/app/QueuedWorkTest.java
+++ b/core/tests/coretests/src/android/app/QueuedWorkTest.java
@@ -163,18 +163,18 @@
@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 523b1b0..bb8356f 100644
--- a/core/tests/coretests/src/android/os/IpcDataCacheTest.java
+++ b/core/tests/coretests/src/android/os/IpcDataCacheTest.java
@@ -27,6 +27,8 @@
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;
@@ -49,6 +51,10 @@
@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";
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 4d64a3a..0000000
--- 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/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index c3bd065..108f5ba 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -211,6 +211,102 @@
}
@Test
+ public void testCalculateInsets_partialSideIntersection_leftCenter() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftTop() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_leftBottom() {
+ mSource.setFrame(new Rect(0, 0, 100, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(100, 0, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topCenter() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topLeft() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_topRight() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightCenter() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightTop() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, -100, 500, 400), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_rightBottom() {
+ mSource.setFrame(new Rect(400, 0, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(0, 100, 500, 600), false);
+ assertEquals(Insets.of(0, 0, 100, 0), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomCenter() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomLeft() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(-100, 0, 400, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
+ public void testCalculateInsets_partialSideIntersection_bottomRight() {
+ mSource.setFrame(new Rect(0, 400, 500, 500));
+ mSource.updateSideHint(new Rect(0, 0, 500, 500));
+ Insets insets = mSource.calculateInsets(new Rect(100, 0, 600, 500), false);
+ assertEquals(Insets.of(0, 0, 0, 100), insets);
+ }
+
+ @Test
public void testCalculateVisibleInsets_override() {
mSource.setFrame(new Rect(0, 0, 500, 100));
mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index ae3ad36..43c404e 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -213,6 +213,35 @@
assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
}
+ @Test
+ public void testMeasureCache() {
+ final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST);
+ final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
+ final Context context = getInstrumentation().getContext();
+ final View child = new View(context);
+ final TestView parent = new TestView(context, 0);
+ parent.addView(child);
+
+ child.setPadding(1, 2, 3, 4);
+ parent.measure(spec1, spec1);
+ assertEquals(4, parent.getMeasuredWidth());
+ assertEquals(6, parent.getMeasuredHeight());
+
+ child.setPadding(5, 6, 7, 8);
+ parent.measure(spec2, spec2);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+
+ // This ends the state of forceLayout.
+ parent.layout(0, 0, 50, 50);
+
+ // The cached values should be cleared after the new setPadding is called. And the measured
+ // width and height should be up-to-date.
+ parent.measure(spec1, spec1);
+ assertEquals(12, parent.getMeasuredWidth());
+ assertEquals(14, parent.getMeasuredHeight());
+ }
+
private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
final ViewParent parent = view.getParent();
@@ -240,6 +269,19 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// We don't layout this view.
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measuredWidth = 0;
+ int measuredHeight = 0;
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ measuredWidth += child.getPaddingLeft() + child.getPaddingRight();
+ measuredHeight += child.getPaddingTop() + child.getPaddingBottom();
+ }
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
}
public static class AutofillableTestView extends TestView {
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 3b9f35b..e6586b3 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 @@
// 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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9ea2943..f0613ce 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 @@
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 b38d00da..1d0c505 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 @@
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/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 21ec84d..9e2d23b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -266,6 +266,8 @@
<dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>
<!-- Width of the expanded bubble bar view shown when the bubble is expanded. -->
<dimen name="bubble_bar_expanded_view_width">412dp</dimen>
+ <!-- Offset of the expanded view when it starts sliding in as part of the switch animation -->
+ <dimen name="bubble_bar_expanded_view_switch_offset">48dp</dimen>
<!-- Minimum width of the bubble bar manage menu. -->
<dimen name="bubble_bar_manage_menu_min_width">200dp</dimen>
<!-- Size of the dismiss icon in the bubble bar manage menu. -->
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 04c17e5..a5205ee 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
@@ -162,6 +162,21 @@
}
/**
+ * 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;
+ }
+
+ /**
* 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 d2cef4b..f269b38 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 @@
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 @@
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 @@
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 @@
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 3046307..77799e9 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 @@
@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 @@
@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 @@
@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 @@
}
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 0000000..84596b0
--- /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 c024840..673d8b3 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
@@ -731,6 +731,8 @@
callback.onBackStarted(backEvent);
if (mBackTransitionHandler.canHandOffAnimation()) {
callback.setHandoffHandler(mHandoffHandler);
+ } else {
+ callback.setHandoffHandler(null);
}
mOnBackStartDispatched = true;
} catch (RemoteException e) {
@@ -1273,14 +1275,6 @@
@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 +1302,8 @@
// 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 +1311,10 @@
}
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 0fd4206..de85d9a 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 @@
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 a313bd0..4d7c7fa 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
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles.bar;
import static android.view.View.ALPHA;
+import static android.view.View.INVISIBLE;
import static android.view.View.SCALE_X;
import static android.view.View.SCALE_Y;
import static android.view.View.TRANSLATION_X;
@@ -25,6 +26,7 @@
import static android.view.View.Y;
import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
+import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_ALPHA;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE;
@@ -32,7 +34,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -42,11 +43,12 @@
import androidx.annotation.Nullable;
+import com.android.app.animation.Interpolators;
+import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
-import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget;
@@ -59,7 +61,7 @@
private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f;
private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
- private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
+ private static final int EXPANDED_VIEW_EXPAND_ALPHA_DURATION = 150;
private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400;
private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400;
private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
@@ -72,6 +74,17 @@
private static final float DISMISS_VIEW_SCALE = 1.25f;
private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100;
+ private static final float SWITCH_OUT_SCALE = 0.97f;
+ private static final long SWITCH_OUT_SCALE_DURATION = 200L;
+ private static final long SWITCH_OUT_ALPHA_DURATION = 100L;
+ private static final long SWITCH_OUT_HANDLE_ALPHA_DURATION = 50L;
+ private static final long SWITCH_IN_ANIM_DELAY = 50L;
+ private static final long SWITCH_IN_TX_DURATION = 350L;
+ private static final long SWITCH_IN_ALPHA_DURATION = 50L;
+ // Keep this handle alpha delay at least as long as alpha animation for both expanded views.
+ private static final long SWITCH_IN_HANDLE_ALPHA_DELAY = 150L;
+ private static final long SWITCH_IN_HANDLE_ALPHA_DURATION = 100L;
+
/** Spring config for the expanded view scale-in animation. */
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
new PhysicsAnimator.SpringConfig(300f, 0.9f);
@@ -80,68 +93,24 @@
private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
new PhysicsAnimator.SpringConfig(900f, 1f);
+ private final int mSwitchAnimPositionOffset;
+
/** Matrix used to scale the expanded view container with a given pivot point. */
private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
- /** Animator for animating the expanded view's alpha (including the TaskView inside it). */
- private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f);
-
@Nullable
- private Animator mRunningDragAnimator;
+ private Animator mRunningAnimator;
- private final Context mContext;
- private final BubbleBarLayerView mLayerView;
private final BubblePositioner mPositioner;
private final int[] mTmpLocation = new int[2];
+ // TODO(b/381936992): remove expanded bubble state from this helper class
private BubbleViewProvider mExpandedBubble;
- private boolean mIsExpanded = false;
- public BubbleBarAnimationHelper(Context context,
- BubbleBarLayerView bubbleBarLayerView,
- BubblePositioner positioner) {
- mContext = context;
- mLayerView = bubbleBarLayerView;
+ public BubbleBarAnimationHelper(Context context, BubblePositioner positioner) {
mPositioner = positioner;
-
- mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION);
- mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // We need to be Z ordered on top in order for alpha animations to work.
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setAnimating(true);
- }
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- // The surface needs to be Z ordered on top for alpha values to work on the
- // TaskView, and if we're temporarily hidden, we are still on the screen
- // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha
- // = 0f remains in effect.
- if (mIsExpanded) {
- bbev.setSurfaceZOrderedOnTop(false);
- }
-
- bbev.setContentVisibility(mIsExpanded);
- bbev.setAnimating(false);
- }
- }
- });
- mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
- BubbleBarExpandedView bbev = getExpandedView();
- if (bbev != null) {
- float alpha = (float) valueAnimator.getAnimatedValue();
- bbev.setTaskViewAlpha(alpha);
- bbev.setAlpha(alpha);
- }
- });
+ mSwitchAnimPositionOffset = context.getResources().getDimensionPixelSize(
+ R.dimen.bubble_bar_expanded_view_switch_offset);
}
/**
@@ -154,18 +123,11 @@
if (bbev == null) {
return;
}
- mIsExpanded = true;
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
- updateExpandedView();
- bbev.setAnimating(true);
- bbev.setSurfaceZOrderedOnTop(true);
- bbev.setContentVisibility(false);
- bbev.setAlpha(0f);
- bbev.setTaskViewAlpha(0f);
- bbev.setVisibility(VISIBLE);
+ prepareForAnimateIn(bbev);
setScaleFromBubbleBar(mExpandedViewContainerMatrix,
1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT);
@@ -173,7 +135,16 @@
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
bbev.animateExpansionWhenTaskViewVisible(() -> {
- mExpandedViewAlphaAnimator.start();
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ true);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
@@ -188,7 +159,7 @@
})
.withEndActions(() -> {
bbev.setAnimationMatrix(null);
- updateExpandedView();
+ updateExpandedView(bbev);
if (afterAnimation != null) {
afterAnimation.run();
}
@@ -197,13 +168,24 @@
});
}
+ private void prepareForAnimateIn(BubbleBarExpandedView bbev) {
+ bbev.setAnimating(true);
+ updateExpandedView(bbev);
+ // We need to be Z ordered on top in order for taskView alpha to work.
+ // It is also set when the alpha animation starts, but needs to be set here to too avoid
+ // flickers.
+ bbev.setSurfaceZOrderedOnTop(true);
+ bbev.setTaskViewAlpha(0f);
+ bbev.setContentVisibility(false);
+ bbev.setVisibility(VISIBLE);
+ }
+
/**
* Collapses the currently expanded bubble.
*
* @param endRunnable a runnable to run at the end of the animation.
*/
public void animateCollapse(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate collapse without a bubble");
@@ -214,6 +196,19 @@
setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f);
+ bbev.setAnimating(true);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION);
+ alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ startNewAnimator(alphaAnim);
+
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
.spring(AnimatableScaleMatrix.SCALE_X,
@@ -234,7 +229,6 @@
}
})
.start();
- mExpandedViewAlphaAnimator.reverse();
}
private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
@@ -246,6 +240,142 @@
}
/**
+ * Animate between two bubble views using a switch animation
+ *
+ * @param fromBubble bubble to hide
+ * @param toBubble bubble to show
+ * @param afterAnimation optional runnable after animation finishes
+ */
+ public void animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble,
+ @Nullable Runnable afterAnimation) {
+ /*
+ * Switch animation
+ *
+ * |.....................fromBubble scale to 0.97.....................|
+ * |fromBubble handle alpha 0|....fromBubble alpha to 0.....| |
+ * 0-------------------------50-----------------------100---150--------200----------250--400
+ * |..toBubble alpha to 1...| |toBubble handle alpha 1| |
+ * |................toBubble position +/-48 to 0...............|
+ */
+
+ mExpandedBubble = toBubble;
+ final BubbleBarExpandedView toBbev = toBubble.getBubbleBarExpandedView();
+ final BubbleBarExpandedView fromBbev = fromBubble.getBubbleBarExpandedView();
+ if (toBbev == null || fromBbev == null) {
+ return;
+ }
+
+ fromBbev.setAnimating(true);
+
+ prepareForAnimateIn(toBbev);
+ final float endTx = toBbev.getTranslationX();
+ final float startTx = getSwitchAnimationInitialTx(endTx);
+ toBbev.setTranslationX(startTx);
+ toBbev.getHandleView().setAlpha(0f);
+
+ toBbev.animateExpansionWhenTaskViewVisible(() -> {
+ AnimatorSet switchAnim = new AnimatorSet();
+ switchAnim.playTogether(switchOutAnimator(fromBbev), switchInAnimator(toBbev, endTx));
+
+ switchAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (afterAnimation != null) {
+ afterAnimation.run();
+ }
+ }
+ });
+ startNewAnimator(switchAnim);
+ });
+ }
+
+ private float getSwitchAnimationInitialTx(float endTx) {
+ if (mPositioner.isBubbleBarOnLeft()) {
+ return endTx - mSwitchAnimPositionOffset;
+ } else {
+ return endTx + mSwitchAnimPositionOffset;
+ }
+ }
+
+ private Animator switchOutAnimator(BubbleBarExpandedView bbev) {
+ setPivotToCenter(bbev);
+ AnimatorSet scaleAnim = new AnimatorSet();
+ scaleAnim.playTogether(
+ ObjectAnimator.ofFloat(bbev, SCALE_X, SWITCH_OUT_SCALE),
+ ObjectAnimator.ofFloat(bbev, SCALE_Y, SWITCH_OUT_SCALE)
+ );
+ scaleAnim.setInterpolator(Interpolators.ACCELERATE);
+ scaleAnim.setDuration(SWITCH_OUT_SCALE_DURATION);
+
+ ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false);
+ alphaAnim.setStartDelay(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+ alphaAnim.setDuration(SWITCH_OUT_ALPHA_DURATION);
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f)
+ .setDuration(SWITCH_OUT_HANDLE_ALPHA_DURATION);
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(scaleAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setAnimating(false);
+ }
+ });
+ return animator;
+ }
+
+ private Animator switchInAnimator(BubbleBarExpandedView bbev, float restingTx) {
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(bbev, TRANSLATION_X, restingTx);
+ positionAnim.setInterpolator(Interpolators.EMPHASIZED_DECELERATE);
+ positionAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ positionAnim.setDuration(SWITCH_IN_TX_DURATION);
+
+ // Animate alpha directly to have finer control over surface z-ordering
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bbev, TASK_VIEW_ALPHA, 1f);
+ alphaAnim.setStartDelay(SWITCH_IN_ANIM_DELAY);
+ alphaAnim.setDuration(SWITCH_IN_ALPHA_DURATION);
+ alphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bbev.setContentVisibility(true);
+ // The outgoing expanded view alpha animation is still in progress.
+ // Do not reset the surface z-order as otherwise the outgoing expanded view is
+ // placed on top.
+ }
+ });
+
+ ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f);
+ handleAlphaAnim.setStartDelay(SWITCH_IN_HANDLE_ALPHA_DELAY);
+ handleAlphaAnim.setDuration(SWITCH_IN_HANDLE_ALPHA_DURATION);
+ handleAlphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ bbev.setSurfaceZOrderedOnTop(false);
+ bbev.setAnimating(false);
+ }
+ });
+
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(positionAnim, alphaAnim, handleAlphaAnim);
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateExpandedView(bbev);
+ }
+ });
+ return animator;
+ }
+
+
+ /**
* Animate the expanded bubble when it is being dragged
*/
public void animateStartDrag() {
@@ -273,7 +403,7 @@
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(contentAnim, handleAnim);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -282,7 +412,6 @@
* @param endRunnable a runnable to run at the end of the animation
*/
public void animateDismiss(Runnable endRunnable) {
- mIsExpanded = false;
final BubbleBarExpandedView bbev = getExpandedView();
if (bbev == null) {
Log.w(TAG, "Trying to animate dismiss without a bubble");
@@ -335,7 +464,7 @@
bbev.setDragging(false);
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -409,7 +538,7 @@
}
}
});
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -435,7 +564,7 @@
animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator(
EMPHASIZED_DECELERATE);
animatorSet.addListener(new DragAnimatorListenerAdapter(bbev));
- startNewDragAnimation(animatorSet);
+ startNewAnimator(animatorSet);
}
/**
@@ -443,14 +572,15 @@
*/
public void cancelAnimations() {
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- mExpandedViewAlphaAnimator.cancel();
BubbleBarExpandedView bbev = getExpandedView();
if (bbev != null) {
bbev.animate().cancel();
}
- if (mRunningDragAnimator != null) {
- mRunningDragAnimator.cancel();
- mRunningDragAnimator = null;
+ if (mRunningAnimator != null) {
+ if (mRunningAnimator.isRunning()) {
+ mRunningAnimator.cancel();
+ }
+ mRunningAnimator = null;
}
}
@@ -462,8 +592,7 @@
return null;
}
- private void updateExpandedView() {
- BubbleBarExpandedView bbev = getExpandedView();
+ private void updateExpandedView(BubbleBarExpandedView bbev) {
if (bbev == null) {
Log.w(TAG, "Trying to update the expanded view without a bubble");
return;
@@ -477,6 +606,8 @@
bbev.setLayoutParams(lp);
bbev.setX(position.x);
bbev.setY(position.y);
+ bbev.setScaleX(1f);
+ bbev.setScaleY(1f);
bbev.updateLocation();
bbev.maybeShowOverflow();
}
@@ -500,18 +631,54 @@
return new Size(width, height);
}
- private void startNewDragAnimation(Animator animator) {
+ private void startNewAnimator(Animator animator) {
cancelAnimations();
- mRunningDragAnimator = animator;
+ mRunningAnimator = animator;
animator.start();
}
+ /**
+ * Animate the alpha of the expanded view between visible (1) and invisible (0).
+ * {@link BubbleBarExpandedView} requires
+ * {@link com.android.wm.shell.bubbles.BubbleExpandedView#setSurfaceZOrderedOnTop(boolean)} to
+ * be called before alpha can be applied.
+ * Only supports alpha of 1 or 0. Otherwise we can't reset surface z-order at the end.
+ */
+ private ObjectAnimator createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView,
+ boolean visible) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(bubbleBarExpandedView, TASK_VIEW_ALPHA,
+ visible ? 1f : 0f);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Move task view to the top of the window so alpha can be applied to it
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(true);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ bubbleBarExpandedView.setContentVisibility(visible);
+ if (!visible) {
+ // Hide the expanded view before we reset the z-ordering
+ bubbleBarExpandedView.setVisibility(INVISIBLE);
+ }
+ bubbleBarExpandedView.setSurfaceZOrderedOnTop(false);
+ }
+ });
+ return animator;
+ }
+
private static void setDragPivot(BubbleBarExpandedView bbev) {
bbev.setPivotX(bbev.getWidth() / 2f);
bbev.setPivotY(0f);
}
- private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
+ private static void setPivotToCenter(BubbleBarExpandedView bbev) {
+ bbev.setPivotX(bbev.getWidth() / 2f);
+ bbev.setPivotY(bbev.getHeight() / 2f);
+ }
+
+ private static class DragAnimatorListenerAdapter extends AnimatorListenerAdapter {
private final BubbleBarExpandedView mBubbleBarExpandedView;
@@ -523,11 +690,9 @@
public void onAnimationStart(Animator animation) {
mBubbleBarExpandedView.setAnimating(true);
}
-
@Override
public void onAnimationEnd(Animator animation) {
mBubbleBarExpandedView.setAnimating(false);
- mRunningDragAnimator = null;
}
}
}
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 ed49417..2c0483c 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
@@ -85,6 +85,22 @@
}
};
+ /**
+ * Property to set alpha for the task view
+ */
+ public static final FloatProperty<BubbleBarExpandedView> TASK_VIEW_ALPHA = new FloatProperty<>(
+ "taskViewAlpha") {
+ @Override
+ public void setValue(BubbleBarExpandedView bbev, float alpha) {
+ bbev.setTaskViewAlpha(alpha);
+ }
+
+ @Override
+ public Float get(BubbleBarExpandedView bbev) {
+ return bbev.mTaskView != null ? bbev.mTaskView.getAlpha() : bbev.getAlpha();
+ }
+ };
+
private static final String TAG = BubbleBarExpandedView.class.getSimpleName();
private static final int INVALID_TASK_ID = -1;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 0c05e3c..425afbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -103,8 +103,7 @@
mPositioner = mBubbleController.getPositioner();
mBubbleLogger = bubbleLogger;
- mAnimationHelper = new BubbleBarAnimationHelper(context,
- this, mPositioner);
+ mAnimationHelper = new BubbleBarAnimationHelper(context, mPositioner);
mEducationViewController = new BubbleEducationViewController(context, (boolean visible) -> {
if (mExpandedView == null) return;
mExpandedView.setObscured(visible);
@@ -183,8 +182,14 @@
// Already showing this bubble, skip animating
return;
}
+ BubbleViewProvider previousBubble = null;
if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) {
- removeView(mExpandedView);
+ if (mIsExpanded) {
+ // Previous expanded view open, keep it visible to animate the switch
+ previousBubble = mExpandedBubble;
+ } else {
+ removeView(mExpandedView);
+ }
mExpandedView = null;
}
if (mExpandedView == null) {
@@ -246,7 +251,8 @@
mIsExpanded = true;
mBubbleController.getSysuiProxy().onStackExpandChanged(true);
- mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
+
+ final Runnable afterAnimation = () -> {
if (mExpandedView == null) return;
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
@@ -256,7 +262,18 @@
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
setTouchDelegate(mHandleTouchDelegate);
- });
+ };
+
+ if (previousBubble != null) {
+ final BubbleBarExpandedView previousExpandedView =
+ previousBubble.getBubbleBarExpandedView();
+ mAnimationHelper.animateSwitch(previousBubble, mExpandedBubble, () -> {
+ removeView(previousExpandedView);
+ afterAnimation.run();
+ });
+ } else {
+ mAnimationHelper.animateExpansion(mExpandedBubble, afterAnimation);
+ }
showScrim(true);
}
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 0000000..498d0e4
--- /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/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 38b8592..ec3c0b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -228,7 +228,6 @@
public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
boolean mImeRequestedVisible =
(WindowInsets.Type.defaultVisible() & WindowInsets.Type.ime()) != 0;
InsetsSourceControl mImeSourceControl = null;
@@ -426,12 +425,10 @@
*/
private void setVisibleDirectly(boolean visible, @Nullable ImeTracker.Token statsToken) {
mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
- mRequestedVisibleTypes = visible
- ? mRequestedVisibleTypes | WindowInsets.Type.ime()
- : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
+ int visibleTypes = visible ? WindowInsets.Type.ime() : 0;
try {
mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
- mRequestedVisibleTypes, statsToken);
+ visibleTypes, WindowInsets.Type.ime(), statsToken);
} catch (RemoteException e) {
}
}
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 736d954..803f16c 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 @@
}
@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 2c2961f..9e5071e 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 @@
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/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index 62d5098..bc56637 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -30,7 +30,7 @@
* desktop windowing environment.
*/
fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+ (isSystemUiTask(context, task) || (task.numActivities > 0 && task.isActivityStackTransparent))
&& !task.isTopActivityNoDisplay
private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
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 819b110..2d0a3f5 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 @@
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 @@
$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 @@
}
)
- "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 @@
}
}
- 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 @@
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 @@
} 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/LetterboxConfiguration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
index 83a8e31..244ff99 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxConfiguration.kt
@@ -36,6 +36,21 @@
// Color resource id for the solid color letterbox background type.
private var letterboxBackgroundColorResourceIdOverride: Int? = null
+ // Default value for corners radius for activities presented in the letterbox mode.
+ // Values < 0 will be ignored.
+ private val letterboxActivityDefaultCornersRadius: Int
+
+ // Current corners radius for activities presented in the letterbox mode.
+ // Values can be modified at runtime and values < 0 will be ignored.
+ private var letterboxActivityCornersRadius = 0
+
+ init {
+ letterboxActivityDefaultCornersRadius = context.resources.getInteger(
+ R.integer.config_letterboxActivityCornersRadius
+ )
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
/**
* Sets color of letterbox background which is used when using the solid background mode.
*/
@@ -64,7 +79,7 @@
}
// Query color dynamically because material colors extracted from wallpaper are updated
// when wallpaper is changed.
- return Color.valueOf(context.getResources().getColor(colorId!!, /* theme */null))
+ return Color.valueOf(context.getResources().getColor(colorId!!, null))
}
/**
@@ -79,4 +94,33 @@
* The background color for the Letterbox.
*/
fun getBackgroundColorRgbArray(): FloatArray = getLetterboxBackgroundColor().components
+
+ /**
+ * Overrides corners radius for activities presented in the letterbox mode. Values < 0,
+ * will be ignored and corners of the activity won't be rounded.
+ */
+ fun setLetterboxActivityCornersRadius(cornersRadius: Int) {
+ letterboxActivityCornersRadius = cornersRadius
+ }
+
+ /**
+ * Resets corners radius for activities presented in the letterbox mode.
+ */
+ fun resetLetterboxActivityCornersRadius() {
+ letterboxActivityCornersRadius = letterboxActivityDefaultCornersRadius
+ }
+
+ /**
+ * Whether corners of letterboxed activities are rounded.
+ */
+ fun isLetterboxActivityCornersRounded(): Boolean {
+ return getLetterboxActivityCornersRadius() > 0
+ }
+
+ /**
+ * Gets corners radius for activities presented in the letterbox mode.
+ */
+ fun getLetterboxActivityCornersRadius(): Int {
+ return maxOf(letterboxActivityCornersRadius, 0)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
index 9e3edf6..0c3769e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategy.kt
@@ -16,6 +16,8 @@
package com.android.wm.shell.compatui.letterbox
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
import com.android.wm.shell.dagger.WMSingleton
import javax.inject.Inject
@@ -24,24 +26,28 @@
* implementing letterbox in shell.
*/
@WMSingleton
-class LetterboxControllerStrategy @Inject constructor() {
+class LetterboxControllerStrategy @Inject constructor(
+ private val letterboxConfiguration: LetterboxConfiguration
+) {
// Different letterbox implementation modes.
enum class LetterboxMode { SINGLE_SURFACE, MULTIPLE_SURFACES }
@Volatile
- private var currentMode: LetterboxMode = LetterboxMode.SINGLE_SURFACE
+ private var currentMode: LetterboxMode = SINGLE_SURFACE
fun configureLetterboxMode() {
// TODO(b/377875146): Define criteria for switching between [LetterboxMode]s.
- currentMode = if (android.os.SystemProperties.getInt(
- "multi_interface",
- 0
- ) == 0
- ) {
- LetterboxMode.SINGLE_SURFACE
+ // At the moment, we use the presence of rounded corners to understand if to use a single
+ // surface or multiple surfaces for the letterbox areas. This rule will change when
+ // considering transparent activities which won't have rounded corners leading to the
+ // [MULTIPLE_SURFACES] option.
+ // The chosen strategy will depend on performance considerations,
+ // including surface memory usage and the impact of the rounded corners solution.
+ currentMode = if (letterboxConfiguration.isLetterboxActivityCornersRounded()) {
+ SINGLE_SURFACE
} else {
- LetterboxMode.MULTIPLE_SURFACES
+ MULTIPLE_SURFACES
}
}
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 0000000..d5e0240
--- /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
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMComponent.java
index a3cdb2e..c493aad 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 @@
/**
* 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 c5644a8..d7ddbde 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 @@
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 @@
}
/**
- * 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 f46b955..f9e3be9 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,8 @@
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;
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
@@ -301,6 +303,7 @@
Transitions transitions,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
Optional<DesktopModeWindowDecorViewModel> desktopModeWindowDecorViewModel) {
if (desktopModeWindowDecorViewModel.isPresent()) {
return desktopModeWindowDecorViewModel.get();
@@ -318,7 +321,8 @@
rootTaskDisplayAreaOrganizer,
syncQueue,
transitions,
- focusTransitionObserver);
+ focusTransitionObserver,
+ windowDecorViewHostSupplier);
}
@WMSingleton
@@ -343,8 +347,13 @@
@WMSingleton
@Provides
- static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier(
+ static WindowDecorViewHostSupplier<WindowDecorViewHost> provideWindowDecorViewHostSupplier(
+ @NonNull Context context,
@ShellMainThread @NonNull CoroutineScope mainScope) {
+ final int poolSize = DesktopModeStatus.getWindowDecorScvhPoolSize(context);
+ if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context) && poolSize > 0) {
+ return new PooledWindowDecorViewHostSupplier(mainScope, poolSize);
+ }
return new DefaultWindowDecorViewHostSupplier(mainScope);
}
@@ -908,6 +917,7 @@
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -927,8 +937,8 @@
displayInsetsController, syncQueue, transitions, desktopTasksController,
desktopImmersiveController.get(),
rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
- assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
- appHandleEducationController, appToWebEducationController,
+ assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper,
+ desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger));
}
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 7764688..50187d5 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 @@
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 80d8ecc..cd37113 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 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 @@
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 d5a2a40..578edd1 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_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 @@
// 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 @@
}
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 @@
}
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()
@@ -1202,9 +1231,12 @@
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 +1285,7 @@
?.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 +1296,9 @@
launchWindowingMode = WINDOWING_MODE_FULLSCREEN
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ launchDisplayId = displayId
+ }
}
val pendingIntent =
PendingIntent.getActivityAsUser(
@@ -1329,6 +1364,21 @@
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 +2098,11 @@
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/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 11110f1..d23c9d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -19,7 +19,6 @@
import android.content.Intent.FILL_IN_COMPONENT
import android.graphics.PointF
import android.graphics.Rect
-import android.os.Bundle
import android.os.IBinder
import android.os.SystemClock
import android.os.SystemProperties
@@ -139,7 +138,11 @@
taskUser,
)
val wct = WindowContainerTransaction()
- wct.sendPendingIntent(pendingIntent, launchHomeIntent, Bundle())
+ // The app that is being dragged into desktop mode might cause new transitions, make this
+ // launch transient to make sure those transitions can execute in parallel and thus won't
+ // block the end-drag transition.
+ val intentOptions = ActivityOptions.makeBasic().setTransientLaunch()
+ wct.sendPendingIntent(pendingIntent, launchHomeIntent, intentOptions.toBundle())
val startTransitionToken =
transitions.startTransition(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, wct, this)
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 9d01535..837a6dd3 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 @@
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 2ae9828..52b6c62 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 @@
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 @@
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 5984d48..a874a5b 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 @@
* @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/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 19428ee..e309da1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -99,7 +99,7 @@
private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
// Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
- private PipAnimationListener mPipRecentsAnimationListener;
+ @Nullable private PipAnimationListener mPipRecentsAnimationListener;
@VisibleForTesting
interface PipAnimationListener {
@@ -378,7 +378,9 @@
tx.setLayer(overlay, Integer.MAX_VALUE);
tx.apply();
}
- mPipRecentsAnimationListener.onPipAnimationStarted();
+ if (mPipRecentsAnimationListener != null) {
+ mPipRecentsAnimationListener.onPipAnimationStarted();
+ }
}
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
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 44cc563..fc3fbe2 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 @@
pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, pipUiEventLogger,
menuController, mainExecutor,
mPipPerfHintController);
- mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);
+ mPipBoundsState.addOnAspectRatioChangedCallback(aspectRatio -> {
+ updateMinMaxSize(aspectRatio);
+ onAspectRatioChanged();
+ });
mMoveOnShelVisibilityChanged = () -> {
if (mIsImeShowing && mImeHeight > mShelfHeight) {
@@ -768,18 +771,19 @@
private void animateToNormalSize(Runnable callback) {
// Save the current bounds as the user-resize bounds.
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
-
- 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);
-
+ 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());
+ return mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds, minMenuSize);
+ }
+
private void animateToUnexpandedState(Rect restoreBounds) {
mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
getMovementBounds(restoreBounds),
@@ -1065,6 +1069,10 @@
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 1a012e0..dae3c21 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 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 @@
@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 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 @@
&& 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 @@
}
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 @@
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 b922cd0..0869caa 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 @@
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 37d5878..032dac9 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 @@
// 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/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 885f3db..0b91966 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -68,6 +68,8 @@
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -90,6 +92,7 @@
private final Transitions mTransitions;
private final Region mExclusionRegion = Region.obtain();
private final InputManager mInputManager;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private TaskOperations mTaskOperations;
private FocusTransitionObserver mFocusTransitionObserver;
@@ -130,7 +133,8 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -143,6 +147,7 @@
mSyncQueue = syncQueue;
mTransitions = transitions;
mFocusTransitionObserver = focusTransitionObserver;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -332,7 +337,8 @@
mMainHandler,
mBgExecutor,
mMainChoreographer,
- mSyncQueue);
+ mSyncQueue,
+ mWindowDecorViewHostSupplier);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final FluidResizeTaskPositioner taskPositioner =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 112e429..23bb2aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -58,6 +58,8 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
/**
@@ -90,9 +92,10 @@
Handler handler,
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
- SyncTransactionQueue syncQueue) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
- taskSurface);
+ SyncTransactionQueue syncQueue,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
+ taskSurface, windowDecorViewHostSupplier);
mHandler = handler;
mBgExecutor = bgExecutor;
mChoreographer = choreographer;
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 e8b02dc..0f5813c 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
@@ -79,6 +79,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.window.DesktopModeFlags;
import android.window.TaskSnapshot;
@@ -139,6 +140,8 @@
import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -217,6 +220,7 @@
private boolean mInImmersiveMode;
private final String mSysUIPackageName;
private final AssistContentRequester mAssistContentRequester;
+ private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
private final ISystemGestureExclusionListener mGestureExclusionListener =
@@ -260,6 +264,7 @@
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
AppHandleEducationController appHandleEducationController,
@@ -289,6 +294,7 @@
desktopImmersiveController,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
@@ -329,6 +335,7 @@
DesktopImmersiveController desktopImmersiveController,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
@@ -381,6 +388,7 @@
mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
DesktopModeWindowDecoration decoration;
RunningTaskInfo taskInfo;
@@ -580,8 +588,16 @@
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(
@@ -755,12 +771,20 @@
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(
@@ -965,8 +989,11 @@
}
if (mInputManager != null
&& !Flags.enableAccessibleCustomHeaders()) {
- // Pilfer so that windows below receive cancellations for this gesture.
- mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
+ ViewRootImpl viewRootImpl = v.getViewRootImpl();
+ if (viewRootImpl != null) {
+ // Pilfer so that windows below receive cancellations for this gesture.
+ mInputManager.pilferPointers(viewRootImpl.getInputToken());
+ }
}
if (isUpOrCancel) {
// Gesture is finished, reset state.
@@ -1607,7 +1634,9 @@
}
final DesktopModeWindowDecoration windowDecoration =
mDesktopModeWindowDecorFactory.create(
- mContext,
+ Flags.enableBugFixesForSecondaryDisplay()
+ ? mDisplayController.getDisplayContext(taskInfo.displayId)
+ : mContext,
mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
mDisplayController,
mSplitScreenController,
@@ -1623,6 +1652,7 @@
mRootTaskDisplayAreaOrganizer,
mGenericLinksParser,
mAssistContentRequester,
+ mWindowDecorViewHostSupplier,
mMultiInstanceHelper,
mWindowDecorCaptionHandleRepository,
mDesktopModeEventLogger);
@@ -1802,11 +1832,10 @@
// 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/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 1263176..6562f38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -105,6 +105,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -219,6 +221,7 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
@@ -230,6 +233,7 @@
WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
context.getSystemService(WindowManager.class)),
new SurfaceControlViewHostFactory() {},
+ windowDecorViewHostSupplier,
DefaultMaximizeMenuFactory.INSTANCE,
DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
windowDecorCaptionHandleRepository, desktopModeEventLogger);
@@ -258,15 +262,16 @@
Supplier<SurfaceControl> surfaceControlSupplier,
WindowManagerWrapper windowManagerWrapper,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
MaximizeMenuFactory maximizeMenuFactory,
HandleMenuFactory handleMenuFactory,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger);
mSplitScreenController = splitScreenController;
mHandler = handler;
mBgExecutor = bgExecutor;
@@ -1760,6 +1765,8 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger) {
@@ -1780,6 +1787,7 @@
rootTaskDisplayAreaOrganizer,
genericLinksParser,
assistContentRequester,
+ windowDecorViewHostSupplier,
multiInstanceHelper,
windowDecorCaptionHandleRepository,
desktopModeEventLogger);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 261d400..5d1bedb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -39,7 +39,6 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Binder;
-import android.os.Handler;
import android.os.Trace;
import android.view.Display;
import android.view.InsetsSource;
@@ -59,10 +58,11 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
-import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
import java.util.ArrayList;
@@ -90,10 +90,10 @@
implements AutoCloseable {
/**
- * The Z-order of {@link #mCaptionContainerSurface}.
+ * The Z-order of the caption surface.
* <p>
* We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
- * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
+ * layering it in front of the caption surface, we can allow it to handle input
* prior to caption view itself, treating corner inputs as resize events rather than
* repositioning.
*/
@@ -102,7 +102,7 @@
* The Z-order of the task input sink in {@link DragPositioningCallback}.
* <p>
* This task input sink is used to prevent undesired dispatching of motion events out of task
- * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
+ * bounds; by layering it behind the caption surface, we allow captions to handle
* input events first.
*/
static final int INPUT_SINK_Z_ORDER = -2;
@@ -123,12 +123,13 @@
final @NonNull Context mUserContext;
final @NonNull DisplayController mDisplayController;
final @NonNull DesktopModeEventLogger mDesktopModeEventLogger;
- private final @ShellMainThread Handler mMainHandler;
final ShellTaskOrganizer mTaskOrganizer;
final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+ @NonNull private final WindowDecorViewHostSupplier<WindowDecorViewHost>
+ mWindowDecorViewHostSupplier;
private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
new DisplayController.OnDisplaysChangedListener() {
@Override
@@ -153,9 +154,7 @@
Display mDisplay;
SurfaceControl mDecorationContainerSurface;
- SurfaceControl mCaptionContainerSurface;
- private CaptionWindowlessWindowManager mCaptionWindowManager;
- private SurfaceControlViewHost mViewHost;
+ private WindowDecorViewHost mViewHost;
private Configuration mWindowDecorConfig;
TaskDragResizer mTaskDragResizer;
boolean mIsCaptionVisible;
@@ -170,20 +169,19 @@
private final Binder mOwner = new Binder();
private final float[] mTmpColor = new float[3];
- private Runnable mCurrentUpdateViewHostRunnable;
-
WindowDecoration(
Context context,
@NonNull Context userContext,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- @ShellMainThread Handler mainHandler,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface) {
- this(context, userContext, displayController, taskOrganizer, mainHandler, taskInfo,
+ SurfaceControl taskSurface,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier) {
+ this(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {}, new DesktopModeEventLogger());
+ new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier,
+ new DesktopModeEventLogger());
}
WindowDecoration(
@@ -191,7 +189,6 @@
@NonNull Context userContext,
@NonNull DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- @ShellMainThread Handler mainHandler,
RunningTaskInfo taskInfo,
@NonNull SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -199,13 +196,13 @@
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
@NonNull DesktopModeEventLogger desktopModeEventLogger
) {
mContext = context;
mUserContext = userContext;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
- mMainHandler = mainHandler;
mTaskInfo = taskInfo;
mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mDesktopModeEventLogger = desktopModeEventLogger;
@@ -213,6 +210,7 @@
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+ mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId);
mIsStatusBarVisible = insetsState != null
@@ -292,43 +290,20 @@
outResult.mCaptionY = 0;
outResult.mCaptionTopPadding = params.mCaptionTopPadding;
+ Trace.beginSection("relayout-createViewHostIfNeeded");
+ createViewHostIfNeeded(mDecorWindowContext, mDisplay);
+ Trace.endSection();
+
Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets");
+ final SurfaceControl captionSurface = mViewHost.getSurfaceControl();
updateDecorationContainerSurface(startT, outResult);
- final SurfaceControl captionSurface = getOrCreateCaptionSurface();
updateCaptionContainerSurface(captionSurface, startT, outResult);
updateCaptionInsets(params, wct, outResult, taskBounds);
updateTaskSurface(params, startT, finishT, outResult);
Trace.endSection();
Trace.beginSection("WindowDecoration#relayout-updateViewHost");
- clearCurrentViewHostRunnable();
- if (params.mAsyncViewHost) {
- mCurrentUpdateViewHostRunnable = () -> updateViewHost(params, startT, outResult);
- mMainHandler.post(mCurrentUpdateViewHostRunnable);
- } else {
- updateViewHost(params, startT, outResult);
- }
- Trace.endSection();
-
- Trace.endSection(); // WindowDecoration#relayout
- }
-
- private void clearCurrentViewHostRunnable() {
- if (mCurrentUpdateViewHostRunnable != null) {
- mMainHandler.removeCallbacks(mCurrentUpdateViewHostRunnable);
- mCurrentUpdateViewHostRunnable = null;
- }
- }
-
- private void updateViewHost(RelayoutParams params, SurfaceControl.Transaction startT,
- RelayoutResult<T> outResult) {
- createCaptionWindowManagerIfNeeded();
- Trace.beginSection("updateViewHost-createViewHostIfNeeded");
- final boolean firstLayout = createViewHostIfNeeded(mDecorWindowContext, mDisplay);
- Trace.endSection();
-
outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0);
- mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
final Rect localCaptionBounds = new Rect(
outResult.mCaptionX,
outResult.mCaptionY,
@@ -337,50 +312,21 @@
final Region touchableRegion = params.mLimitTouchRegionToSystemAreas
? calculateLimitedTouchableRegion(params, localCaptionBounds)
: null;
- if (params.mLimitTouchRegionToSystemAreas) {
- mCaptionWindowManager.setTouchRegion(mViewHost, touchableRegion);
- }
- if (touchableRegion != null) {
- touchableRegion.recycle();
- }
- updateViewHierarchy(params, outResult, startT, firstLayout);
+ updateViewHierarchy(params, outResult, startT, touchableRegion);
+ Trace.endSection();
+
+ Trace.endSection(); // WindowDecoration#relayout
}
- private SurfaceControl getOrCreateCaptionSurface() {
- if (mCaptionContainerSurface == null) {
- final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
- mCaptionContainerSurface = builder
- .setName("Caption container of Task=" + mTaskInfo.taskId)
- .setContainerLayer()
- .setParent(mDecorationContainerSurface)
- .setCallsite("WindowDecoration.updateCaptionContainerSurface")
- .build();
- }
- return mCaptionContainerSurface;
- }
-
- private boolean createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
+ private void createViewHostIfNeeded(@NonNull Context context, @NonNull Display display) {
if (mViewHost == null) {
- mViewHost = mSurfaceControlViewHostFactory.create(context, display,
- mCaptionWindowManager);
- Trace.endSection();
- return true;
- }
- return false;
- }
-
- private void createCaptionWindowManagerIfNeeded() {
- if (mCaptionWindowManager == null) {
- // Put caption under a container surface because ViewRootImpl sets the destination frame
- // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
- mCaptionWindowManager = new CaptionWindowlessWindowManager(
- mTaskInfo.getConfiguration(), mCaptionContainerSurface);
+ mViewHost = mWindowDecorViewHostSupplier.acquire(context, display);
}
}
private void updateViewHierarchy(@NonNull RelayoutParams params,
@NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT,
- boolean firstLayout) {
+ @Nullable Region touchableRegion) {
Trace.beginSection("WindowDecoration#updateViewHierarchy");
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(
@@ -392,16 +338,15 @@
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
lp.inputFeatures = params.mInputFeatures;
- if (params.mApplyStartTransactionOnDraw) {
- if (params.mAsyncViewHost) {
+ if (params.mAsyncViewHost) {
+ if (params.mApplyStartTransactionOnDraw) {
throw new IllegalArgumentException("Cannot use sync draw tx with async relayout");
}
- mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
- }
- if (firstLayout) {
- mViewHost.setView(outResult.mRootView, lp);
+ mViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion);
} else {
- mViewHost.relayout(lp);
+ mViewHost.updateView(outResult.mRootView, lp, mTaskInfo.configuration,
+ touchableRegion, params.mApplyStartTransactionOnDraw ? startT : null);
}
Trace.endSection();
}
@@ -719,18 +664,11 @@
}
void releaseViews(WindowContainerTransaction wct) {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- mCaptionWindowManager = null;
-
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
boolean released = false;
- if (mCaptionContainerSurface != null) {
- t.remove(mCaptionContainerSurface);
- mCaptionContainerSurface = null;
+ if (mViewHost != null) {
+ mWindowDecorViewHostSupplier.release(mViewHost, t);
+ mViewHost = null;
released = true;
}
@@ -753,7 +691,6 @@
@Override
public void close() {
Trace.beginSection("WindowDecoration#close");
- clearCurrentViewHostRunnable();
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
releaseViews(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
index c470eef..a205ac6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHost.kt
@@ -17,12 +17,11 @@
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Region
import android.view.Display
import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
-import android.view.WindowlessWindowManager
import androidx.tracing.Trace
import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.shared.annotations.ShellMainThread
@@ -30,51 +29,34 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-typealias SurfaceControlViewHostFactory =
- (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
-
/**
- * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost].
+ * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHostAdapter].
*
- * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
- * any attempts to do will throw, which means that once a [View] is added using [updateView] or
- * [updateViewAsync], only its properties and binding may be changed, its children views may be
- * added, removed or changed and its [WindowManager.LayoutParams] may be changed. It also supports
- * asynchronously updating the view hierarchy using [updateViewAsync], in which case the update work
- * will be posted on the [ShellMainThread] with no delay.
+ * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which
+ * case the update work will be posted on the [ShellMainThread] with no delay.
*/
class DefaultWindowDecorViewHost(
- private val context: Context,
+ context: Context,
@ShellMainThread private val mainScope: CoroutineScope,
- private val display: Display,
- private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
- SurfaceControlViewHost(c, d, wwm, s)
- },
+ display: Display,
+ @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter =
+ SurfaceControlViewHostAdapter(context, display),
) : WindowDecorViewHost {
-
- private val rootSurface: SurfaceControl =
- SurfaceControl.Builder()
- .setName("DefaultWindowDecorViewHost surface")
- .setContainerLayer()
- .setCallsite("DefaultWindowDecorViewHost#init")
- .build()
-
- private var wwm: WindowlessWindowManager? = null
- @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
private var currentUpdateJob: Job? = null
override val surfaceControl: SurfaceControl
- get() = rootSurface
+ get() = viewHostAdapter.rootSurface
override fun updateView(
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
onDrawTransaction: SurfaceControl.Transaction?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateView")
clearCurrentUpdateJob()
- updateViewHost(view, attrs, configuration, onDrawTransaction)
+ updateViewHost(view, attrs, configuration, touchableRegion, onDrawTransaction)
Trace.endSection()
}
@@ -82,53 +64,41 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync")
clearCurrentUpdateJob()
currentUpdateJob =
mainScope.launch {
- updateViewHost(view, attrs, configuration, onDrawTransaction = null)
+ updateViewHost(
+ view,
+ attrs,
+ configuration,
+ touchableRegion,
+ onDrawTransaction = null
+ )
}
Trace.endSection()
}
override fun release(t: SurfaceControl.Transaction) {
clearCurrentUpdateJob()
- viewHost?.release()
- t.remove(rootSurface)
+ viewHostAdapter.release(t)
}
private fun updateViewHost(
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
+ touchableRegion: Region?,
onDrawTransaction: SurfaceControl.Transaction?,
) {
Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost")
- if (wwm == null) {
- wwm = WindowlessWindowManager(configuration, rootSurface, null)
+ viewHostAdapter.prepareViewHost(configuration, touchableRegion)
+ onDrawTransaction?.let {
+ viewHostAdapter.applyTransactionOnDraw(it)
}
- requireWindowlessWindowManager().setConfiguration(configuration)
- if (viewHost == null) {
- viewHost =
- surfaceControlViewHostFactory.invoke(
- context,
- display,
- requireWindowlessWindowManager(),
- "DefaultWindowDecorViewHost#updateViewHost",
- )
- }
- onDrawTransaction?.let { requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) }
- if (requireViewHost().view == null) {
- Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView")
- requireViewHost().setView(view, attrs)
- Trace.endSection()
- } else {
- check(requireViewHost().view == view) { "Changing view is not allowed" }
- Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout")
- requireViewHost().relayout(attrs)
- Trace.endSection()
- }
+ viewHostAdapter.updateView(view, attrs)
Trace.endSection()
}
@@ -136,12 +106,4 @@
currentUpdateJob?.cancel()
currentUpdateJob = null
}
-
- private fun requireWindowlessWindowManager(): WindowlessWindowManager {
- return wwm ?: error("Expected non-null windowless window manager")
- }
-
- private fun requireViewHost(): SurfaceControlViewHost {
- return viewHost ?: error("Expected non-null view host")
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
index 27ffd6c..7821619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostSupplier.kt
@@ -24,14 +24,15 @@
/**
* A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested.
*/
-class DefaultWindowDecorViewHostSupplier(@ShellMainThread private val mainScope: CoroutineScope) :
- WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> {
+class DefaultWindowDecorViewHostSupplier(
+ @ShellMainThread private val mainScope: CoroutineScope
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
- override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost {
+ override fun acquire(context: Context, display: Display): WindowDecorViewHost {
return DefaultWindowDecorViewHost(context, mainScope, display)
}
- override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) {
+ override fun release(viewHost: WindowDecorViewHost, t: SurfaceControl.Transaction) {
viewHost.release(t)
}
}
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 0000000..adb0ba6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplier.kt
@@ -0,0 +1,70 @@
+/*
+ * 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 kotlinx.coroutines.CoroutineScope
+
+/**
+ * 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.
+ */
+class PooledWindowDecorViewHostSupplier(
+ @ShellMainThread private val mainScope: CoroutineScope,
+ maxPoolSize: Int,
+) : WindowDecorViewHostSupplier<WindowDecorViewHost> {
+
+ private val pool: Pools.Pool<WindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize)
+ private var nextDecorViewHostId = 0
+
+ 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 0000000..bf0b118
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHost.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.Region
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.View
+import android.view.WindowManager
+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.
+ */
+class ReusableWindowDecorViewHost(
+ private val context: Context,
+ @ShellMainThread private val mainScope: CoroutineScope,
+ display: Display,
+ val id: Int,
+ @VisibleForTesting
+ val viewHostAdapter: SurfaceControlViewHostAdapter =
+ SurfaceControlViewHostAdapter(context, display),
+) : WindowDecorViewHost {
+ @VisibleForTesting val rootView = FrameLayout(context)
+
+ private var currentUpdateJob: Job? = null
+
+ override val surfaceControl: SurfaceControl
+ get() = viewHostAdapter.rootSurface
+
+ 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/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
new file mode 100644
index 0000000..26a43f4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapter.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.Region
+import android.view.AttachedSurfaceControl
+import android.view.Display
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import androidx.tracing.Trace
+import com.android.internal.annotations.VisibleForTesting
+
+typealias SurfaceControlViewHostFactory =
+ (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost
+
+/**
+ * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl].
+ *
+ * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and
+ * any attempts to do will throw, which means that once a [View] is added using [updateView], only
+ * its properties and binding may be changed, children views may be added, removed or changed
+ * and its [WindowManager.LayoutParams] may be changed.
+ */
+class SurfaceControlViewHostAdapter(
+ private val context: Context,
+ private val display: Display,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s ->
+ SurfaceControlViewHost(c, d, wwm, s)
+ },
+) {
+ val rootSurface: SurfaceControl =
+ SurfaceControl.Builder()
+ .setName("SurfaceControlViewHostAdapter surface")
+ .setContainerLayer()
+ .setCallsite("SurfaceControlViewHostAdapter#init")
+ .build()
+
+ private var wwm: WindowDecorWindowlessWindowManager? = null
+ @VisibleForTesting var viewHost: SurfaceControlViewHost? = null
+
+ /**
+ * Initialize or updates the [SurfaceControlViewHost].
+ */
+ fun prepareViewHost(
+ configuration: Configuration,
+ touchableRegion: Region?
+ ) {
+ if (wwm == null) {
+ wwm = WindowDecorWindowlessWindowManager(configuration, rootSurface)
+ }
+ if (viewHost == null) {
+ viewHost =
+ surfaceControlViewHostFactory.invoke(
+ context,
+ display,
+ requireWindowlessWindowManager(),
+ "SurfaceControlViewHostAdapter#prepareViewHost",
+ )
+ }
+ requireWindowlessWindowManager().setConfiguration(configuration)
+ requireWindowlessWindowManager().setTouchRegion(requireViewHost(), touchableRegion)
+ }
+
+ /**
+ * Request to apply the transaction atomically with the next draw of the view hierarchy. See
+ * [AttachedSurfaceControl.applyTransactionOnDraw].
+ */
+ fun applyTransactionOnDraw(t: SurfaceControl.Transaction) {
+ requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t)
+ }
+
+ /** Update the view hierarchy of the view host. */
+ fun updateView(view: View, attrs: WindowManager.LayoutParams) {
+ if (requireViewHost().view == null) {
+ Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView")
+ requireViewHost().setView(view, attrs)
+ Trace.endSection()
+ } else {
+ check(requireViewHost().view == view) { "Changing view is not allowed" }
+ Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout")
+ requireViewHost().relayout(attrs)
+ Trace.endSection()
+ }
+ }
+
+ /** Release the view host and remove the backing surface. */
+ fun release(t: SurfaceControl.Transaction) {
+ viewHost?.release()
+ t.remove(rootSurface)
+ }
+
+ /** Whether the view host has had a view hierarchy set. */
+ fun isInitialized(): Boolean = viewHost?.view != null
+
+ private fun requireWindowlessWindowManager(): WindowDecorWindowlessWindowManager {
+ return wwm ?: error("Expected non-null windowless window manager")
+ }
+
+ private fun requireViewHost(): SurfaceControlViewHost {
+ return viewHost ?: error("Expected non-null view host")
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
index 7c1479e..2dcbbac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorViewHost.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor.common.viewhost
import android.content.res.Configuration
+import android.graphics.Region
import android.view.SurfaceControl
import android.view.View
import android.view.WindowManager
@@ -34,11 +35,17 @@
view: View,
attrs: WindowManager.LayoutParams,
configuration: Configuration,
- onDrawTransaction: SurfaceControl.Transaction?,
+ touchableRegion: Region? = null,
+ onDrawTransaction: SurfaceControl.Transaction? = null,
)
/** Asynchronously update the view hierarchy of this view host. */
- fun updateViewAsync(view: View, attrs: WindowManager.LayoutParams, configuration: Configuration)
+ fun updateViewAsync(
+ view: View,
+ attrs: WindowManager.LayoutParams,
+ configuration: Configuration,
+ touchableRegion: Region? = null,
+ )
/** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */
fun release(t: SurfaceControl.Transaction)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
new file mode 100644
index 0000000..fbe8c6c8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/viewhost/WindowDecorWindowlessWindowManager.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor.common.viewhost
+
+import android.content.res.Configuration
+import android.graphics.Region
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.WindowlessWindowManager
+
+/**
+ * A [WindowlessWindowManager] for the window decor caption that allows customizing the touchable
+ * region.
+ */
+class WindowDecorWindowlessWindowManager(
+ configuration: Configuration,
+ rootSurface: SurfaceControl,
+) : WindowlessWindowManager(configuration, rootSurface, /* hostInputTransferToken= */ null) {
+
+ /** Set the view host's touchable region. */
+ fun setTouchRegion(viewHost: SurfaceControlViewHost, region: Region?) {
+ setTouchRegion(viewHost.windowToken.asBinder(), region)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 310c2d7..ec3fe95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -54,6 +54,7 @@
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
private boolean mIsTopActivityTransparent = false;
+ private boolean mIsActivityStackTransparent = false;
private int mNumActivities = 1;
private long mLastActiveTime;
@@ -158,6 +159,12 @@
return this;
}
+ public TestRunningTaskInfoBuilder setActivityStackTransparent(
+ boolean isActivityStackTransparent) {
+ mIsActivityStackTransparent = isActivityStackTransparent;
+ return this;
+ }
+
public TestRunningTaskInfoBuilder setNumActivities(int numActivities) {
mNumActivities = numActivities;
return this;
@@ -187,6 +194,7 @@
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
info.isTopActivityTransparent = mIsTopActivityTransparent;
+ info.isActivityStackTransparent = mIsActivityStackTransparent;
info.numActivities = mNumActivities;
info.lastActiveTime = mLastActiveTime;
info.userId = mUserId;
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 f22e2a5..a2afd2c 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.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 @@
releaseBackGesture();
mShellExecutor.flushAll();
- verify(mAppCallback).setHandoffHandler(any());
+ verify(mAppCallback).setHandoffHandler(notNull());
}
@Test
@@ -654,7 +656,7 @@
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/common/HandlerExecutorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/HandlerExecutorTest.kt
new file mode 100644
index 0000000..799b48c
--- /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/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index 1d39000..d52fd4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -37,35 +37,46 @@
@SmallTest
class AppCompatUtilsTest : ShellTestCase() {
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent() {
+ fun testIsTopActivityExemptFromDesktopWindowing_onlyTransparentActivitiesInStack() {
assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 1
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 1
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
+ fun testIsTopActivityExemptFromDesktopWindowing_noActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
- numActivities = 2
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
+ numActivities = 0
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
+ fun testIsTopActivityExemptFromDesktopWindowing_nonTransparentActivitiesInStack() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
.apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = false
+ isTopActivityNoDisplay = false
numActivities = 1
+ }))
+ }
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_transparentActivityStack_notDisplayed() {
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
+ numActivities = 1
}))
}
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 94dbd11..4c97c76 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 @@
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.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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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/LetterboxConfigurationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
index 75025d90..1399600 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxConfigurationTest.kt
@@ -26,6 +26,7 @@
import com.android.internal.R
import com.android.wm.shell.ShellTestCase
import java.util.function.Consumer
+import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
@@ -51,6 +52,14 @@
val COLOR_WHITE_RESOURCE_ID = android.R.color.white
@JvmStatic
val COLOR_BLACK_RESOURCE_ID = android.R.color.black
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_DEFAULT = 32
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_VALID = 16
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_NONE = 0
+ @JvmStatic
+ val ROUNDED_CORNER_RADIUS_INVALID = -10
}
@Test
@@ -112,6 +121,68 @@
}
}
+ @Test
+ fun `default rounded corner radius is used if override is not set`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
+ @Test
+ fun `new rounded corner radius is used after setting a valid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ }
+ }
+
+ @Test
+ fun `no rounded corner radius is used after setting an invalid value`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ }
+ }
+
+ @Test
+ fun `has rounded corners for different values`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkIsLetterboxActivityCornersRounded(true)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_INVALID)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_NONE)
+ r.checkIsLetterboxActivityCornersRounded(false)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkIsLetterboxActivityCornersRounded(true)
+ }
+ }
+
+ @Test
+ fun `reset rounded corners radius`() {
+ runTestScenario { r ->
+ r.setDefaultRoundedCornerRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ r.loadConfiguration()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+
+ r.overrideRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_VALID)
+
+ r.resetRoundedCornersRadius()
+ r.checkRoundedCornersRadius(ROUNDED_CORNER_RADIUS_DEFAULT)
+ }
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -135,6 +206,11 @@
.getColor(R.color.config_letterboxBackgroundColor, null)
}
+ fun setDefaultRoundedCornerRadius(radius: Int) {
+ doReturn(radius).`when`(resources)
+ .getInteger(R.integer.config_letterboxActivityCornersRadius)
+ }
+
fun loadConfiguration() {
letterboxConfig = LetterboxConfiguration(ctx)
}
@@ -147,14 +223,30 @@
letterboxConfig.resetLetterboxBackgroundColor()
}
+ fun resetRoundedCornersRadius() {
+ letterboxConfig.resetLetterboxActivityCornersRadius()
+ }
+
fun overrideBackgroundColorId(@ColorRes colorId: Int) {
letterboxConfig.setLetterboxBackgroundColorResourceId(colorId)
}
+ fun overrideRoundedCornersRadius(radius: Int) {
+ letterboxConfig.setLetterboxActivityCornersRadius(radius)
+ }
+
fun checkBackgroundColor(expected: Color) {
val colorComponents = letterboxConfig.getBackgroundColorRgbArray()
val expectedComponents = expected.components
assert(expectedComponents.contentEquals(colorComponents))
}
+
+ fun checkRoundedCornersRadius(expected: Int) {
+ assertEquals(expected, letterboxConfig.getLetterboxActivityCornersRadius())
+ }
+
+ fun checkIsLetterboxActivityCornersRounded(expected: Boolean) {
+ assertEquals(expected, letterboxConfig.isLetterboxActivityCornersRounded())
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
new file mode 100644
index 0000000..50fdf45
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerStrategyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.MULTIPLE_SURFACES
+import com.android.wm.shell.compatui.letterbox.LetterboxControllerStrategy.LetterboxMode.SINGLE_SURFACE
+import java.util.function.Consumer
+import kotlin.test.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests for [LetterboxControllerStrategy].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxControllerStrategyTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxControllerStrategyTest : ShellTestCase() {
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(true)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsSingle()
+ }
+ }
+
+ @Test
+ fun `LetterboxMode is MULTIPLE_SURFACES with no rounded corners`() {
+ runTestScenario { r ->
+ r.configureRoundedCornerRadius(false)
+ r.configureLetterboxMode()
+ r.checkLetterboxModeIsMultiple()
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxStrategyRobotTest>) {
+ val robot = LetterboxStrategyRobotTest(mContext)
+ consumer.accept(robot)
+ }
+
+ class LetterboxStrategyRobotTest(val ctx: Context) {
+
+ companion object {
+ @JvmStatic
+ private val ROUNDED_CORNERS_TRUE = 10
+ @JvmStatic
+ private val ROUNDED_CORNERS_FALSE = 0
+ }
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val letterboxStrategy: LetterboxControllerStrategy
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ letterboxStrategy = LetterboxControllerStrategy(letterboxConfiguration)
+ }
+
+ fun configureRoundedCornerRadius(enabled: Boolean) {
+ letterboxConfiguration.setLetterboxActivityCornersRadius(
+ if (enabled) ROUNDED_CORNERS_TRUE else ROUNDED_CORNERS_FALSE
+ )
+ }
+
+ fun configureLetterboxMode() {
+ letterboxStrategy.configureLetterboxMode()
+ }
+
+ fun checkLetterboxModeIsSingle(expected: Boolean = true) {
+ val expectedMode = if (expected) SINGLE_SURFACE else MULTIPLE_SURFACES
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+
+ fun checkLetterboxModeIsMultiple(expected: Boolean = true) {
+ val expectedMode = if (expected) MULTIPLE_SURFACES else SINGLE_SURFACE
+ assertEquals(expectedMode, letterboxStrategy.getLetterboxImplementationMode())
+ }
+ }
+}
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 8e21071..6a4ad2e 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.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
@@ -1151,7 +1152,7 @@
fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -1167,7 +1168,7 @@
fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -1724,6 +1725,34 @@
}
@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()
@@ -2260,7 +2289,7 @@
val task =
setUpFullscreenTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = true
numActivities = 1
}
@@ -2278,7 +2307,7 @@
val task =
setUpFreeformTask().apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -4228,6 +4257,14 @@
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/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 866d1b3..aee8821 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -73,10 +73,10 @@
.setLastActiveTime(100)
.build()
- /** Create a new System Modal task, i.e. a task with a single transparent activity. */
+ /** Create a new System Modal task, i.e. a task with only transparent activities. */
fun createSystemModalTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo =
createFullscreenTaskBuilder(displayId)
- .setTopActivityTransparent(true)
+ .setActivityStackTransparent(true)
.setNumActivities(1)
.build()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
index b9d7bbf..c33005e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/compatui/SystemModalsTransitionHandlerTest.kt
@@ -43,6 +43,10 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+/**
+ * Tests for {@link SystemModalsTransitionHandler}
+ * Usage: atest WMShellUnitTests:SystemModalsTransitionHandlerTest
+ */
@SmallTest
@RunWith(AndroidTestingRunner::class)
class SystemModalsTransitionHandlerTest : ShellTestCase() {
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 c6835b7..22b45e8 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_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 @@
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 a15b611..8b4cf6d 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 @@
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 a4e3af4..88f62d1 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
@@ -278,7 +278,7 @@
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForTopTranslucentActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- isTopActivityTransparent = true
+ isActivityStackTransparent = true
isTopActivityNoDisplay = false
numActivities = 1
}
@@ -780,6 +780,8 @@
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 afd4607..6be234e 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
@@ -74,6 +74,8 @@
import com.android.wm.shell.util.StubTransaction
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder
import org.junit.After
import org.junit.Rule
@@ -131,6 +133,8 @@
protected val mockAssistContentRequester = mock<AssistContentRequester>()
protected val bgExecutor = TestShellExecutor()
protected val mockMultiInstanceHelper = mock<MultiInstanceHelper>()
+ private val mockWindowDecorViewHostSupplier =
+ mock<WindowDecorViewHostSupplier<WindowDecorViewHost>>()
protected val mockTasksLimiter = mock<DesktopTasksLimiter>()
protected val mockFreeformTaskTransitionStarter = mock<FreeformTaskTransitionStarter>()
protected val mockActivityOrientationChangeHandler =
@@ -193,6 +197,7 @@
mockDesktopImmersiveController,
mockGenericLinksParser,
mockAssistContentRequester,
+ mockWindowDecorViewHostSupplier,
mockMultiInstanceHelper,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
@@ -281,6 +286,7 @@
} else {
statusBars()
}
+ userId = context.userId
}
}
@@ -289,7 +295,7 @@
whenever(
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any(), any(), any(), any(), any(), any(), any())
+ any(), any(), any(), any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 8c77775..5d5d1f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -113,6 +113,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import kotlin.Unit;
@@ -185,6 +187,10 @@
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
@@ -274,6 +280,9 @@
any())).thenReturn(mMockAppHeaderViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay)))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@After
@@ -1722,7 +1731,7 @@
mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new,
mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
- maximizeMenuFactory, mMockHandleMenuFactory,
+ mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 3a82ff6..d969346 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -89,6 +89,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.tests.R;
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
+import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
import org.junit.Before;
import org.junit.Rule;
@@ -131,6 +133,10 @@
@Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
+ private WindowDecorViewHostSupplier<WindowDecorViewHost> mMockWindowDecorViewHostSupplier;
+ @Mock
+ private WindowDecorViewHost mMockWindowDecorViewHost;
+ @Mock
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
private AttachedSurfaceControl mMockRootSurfaceControl;
@@ -180,6 +186,10 @@
// Add status bar inset so that WindowDecoration does not think task is in immersive mode
mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+
+ when(mMockWindowDecorViewHostSupplier.acquire(any(), any()))
+ .thenReturn(mMockWindowDecorViewHost);
+ when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class));
}
@Test
@@ -236,10 +246,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
@@ -260,18 +266,19 @@
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
-
- verify(mMockSurfaceControlViewHost)
- .setView(same(mMockView),
- argThat(lp -> lp.height == 64
- && lp.width == 300
- && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+ verify(mMockWindowDecorViewHost).updateView(
+ same(mMockView),
+ argThat(lp -> lp.height == 64
+ && lp.width == 300
+ && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0),
+ eq(taskInfo.configuration),
+ any(),
+ eq(null) /* onDrawTransaction */);
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction).addInsetsSource(
eq(taskInfo.token),
@@ -300,10 +307,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -326,7 +329,7 @@
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(mMockSurfaceControlViewHost, never()).release();
+ verify(mMockWindowDecorViewHost, never()).release(any());
verify(t, never()).apply();
verify(mMockWindowContainerTransaction, never())
.removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -336,9 +339,8 @@
taskInfo.isVisible = false;
windowDecor.relayout(taskInfo, false /* hasGlobalFocus */);
- final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
- releaseOrder.verify(mMockSurfaceControlViewHost).release();
- releaseOrder.verify(t2).remove(captionContainerSurface);
+ final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier);
+ releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2);
releaseOrder.verify(t2).remove(decorContainerSurface);
releaseOrder.verify(t2).apply();
// Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
@@ -386,8 +388,8 @@
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
- verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+ verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay));
+ verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any(), any());
}
@Test
@@ -400,10 +402,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -439,8 +437,7 @@
windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
- verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
- .create(any(), eq(defaultDisplay), any());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
}
@Test
@@ -453,10 +450,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -475,8 +468,8 @@
windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
- verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
- verify(captionContainerSurfaceBuilder).setContainerLayer();
+ final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl();
+ verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -492,10 +485,6 @@
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
- final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
- final SurfaceControl.Builder captionContainerSurfaceBuilder =
- createMockSurfaceControlBuilder(captionContainerSurface);
- mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
@@ -512,10 +501,11 @@
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
- windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */,
- true /* hasGlobalFocus */, Region.obtain());
+ mRelayoutParams.mApplyStartTransactionOnDraw = true;
+ windowDecor.relayout(taskInfo, true /* hasGlobalFocus */, Region.obtain());
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), any(),
+ eq(mMockSurfaceControlStartT));
}
@Test
@@ -918,7 +908,13 @@
/* hasGlobalFocus= */ true,
Region.obtain());
- verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT);
+ verify(mMockWindowDecorViewHost)
+ .updateView(
+ eq(mRelayoutResult.mRootView),
+ any(),
+ eq(windowDecor.mTaskInfo.configuration),
+ any(),
+ eq(mMockSurfaceControlStartT));
windowDecor.close();
}
@@ -933,15 +929,11 @@
mRelayoutParams.mAsyncViewHost = true;
mRelayoutResult.mRootView = mMockView;
- windowDecor.relayout(
- windowDecor.mTaskInfo,
- /* hasGlobalFocus= */ true,
- Region.obtain());
- final ArgumentCaptor<Runnable> updateViewHostCaptor =
- ArgumentCaptor.forClass(Runnable.class);
- verify(mMockHandler).post(updateViewHostCaptor.capture());
assertThrows(IllegalArgumentException.class,
- () -> updateViewHostCaptor.getValue().run());
+ () -> windowDecor.relayout(
+ windowDecor.mTaskInfo,
+ /* hasGlobalFocus= */ true,
+ Region.obtain()));
windowDecor.close();
}
@@ -961,13 +953,9 @@
/* hasGlobalFocus= */ true,
Region.obtain());
- final ArgumentCaptor<Runnable> updateViewHostCaptor =
- ArgumentCaptor.forClass(Runnable.class);
- verify(mMockHandler).post(updateViewHostCaptor.capture());
-
- updateViewHostCaptor.getValue().run();
-
- verify(mMockSurfaceControlViewHost).setView(eq(mMockView), any());
+ verify(mMockWindowDecorViewHost)
+ .updateViewAsync(eq(mRelayoutResult.mRootView), any(),
+ eq(windowDecor.mTaskInfo.configuration), any());
windowDecor.close();
}
@@ -1046,13 +1034,14 @@
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mContext, mMockDisplayController,
- mMockShellTaskOrganizer, mMockHandler, taskInfo, mMockTaskSurface,
+ mMockShellTaskOrganizer, taskInfo, mMockTaskSurface,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
() -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
- mMockSurfaceControlViewHostFactory, mDesktopModeEventLogger);
+ mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier,
+ mDesktopModeEventLogger);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -1087,7 +1076,6 @@
TestWindowDecoration(Context context, @NonNull Context userContext,
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
- Handler handler,
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
@@ -1095,11 +1083,14 @@
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
+ windowDecorViewHostSupplier,
DesktopModeEventLogger desktopModeEventLogger) {
- super(context, userContext, displayController, taskOrganizer, handler, taskInfo,
+ super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
- surfaceControlViewHostFactory, desktopModeEventLogger);
+ surfaceControlViewHostFactory, windowDecorViewHostSupplier,
+ desktopModeEventLogger);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
index 2f223de..4f19f34 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/DefaultWindowDecorViewHostTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.SurfaceControl
-import android.view.SurfaceControlViewHost
import android.view.View
import android.view.WindowManager
import androidx.test.filters.SmallTest
@@ -28,7 +27,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -57,54 +55,8 @@
onDrawTransaction = null,
)
- assertThat(windowDecorViewHost.viewHost).isNotNull()
- assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
- }
-
- @Test
- fun updateView_alreadyLaidOut_relayouts() = runTest {
- val windowDecorViewHost = createDefaultViewHost()
- val view = View(context)
- windowDecorViewHost.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- onDrawTransaction = null,
- )
-
- val otherParams = WindowManager.LayoutParams(200, 200)
- windowDecorViewHost.updateView(
- view = view,
- attrs = otherParams,
- configuration = context.resources.configuration,
- onDrawTransaction = null,
- )
-
- assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view)
- assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
- .isEqualTo(otherParams.width)
- }
-
- @Test
- fun updateView_replacingView_throws() = runTest {
- val windowDecorViewHost = createDefaultViewHost()
- val view = View(context)
- windowDecorViewHost.updateView(
- view = view,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- onDrawTransaction = null,
- )
-
- val otherView = View(context)
- assertThrows(Exception::class.java) {
- windowDecorViewHost.updateView(
- view = otherView,
- attrs = WindowManager.LayoutParams(100, 100),
- configuration = context.resources.configuration,
- onDrawTransaction = null,
- )
- }
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isEqualTo(view)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -123,7 +75,7 @@
)
// No view host yet, since the coroutine hasn't run.
- assertThat(windowDecorViewHost.viewHost).isNull()
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
windowDecorViewHost.updateView(
view = syncView,
@@ -135,14 +87,13 @@
// Would run coroutine if it hadn't been cancelled.
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHost).isNotNull()
- assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isNotNull()
// View host view/attrs should match the ones from the sync call, plus, since the
// sync/async were made with different views, if the job hadn't been cancelled there
// would've been an exception thrown as replacing views isn't allowed.
- assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView)
- assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width)
- .isEqualTo(syncAttrs.width)
+ assertThat(windowDecorViewHost.view()).isEqualTo(syncView)
+ assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -158,11 +109,11 @@
configuration = context.resources.configuration,
)
- assertThat(windowDecorViewHost.viewHost).isNull()
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse()
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHost).isNotNull()
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -185,9 +136,8 @@
advanceUntilIdle()
- assertThat(windowDecorViewHost.viewHost).isNotNull()
- assertThat(windowDecorViewHost.viewHost!!.view).isNotNull()
- assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView)
+ assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue()
+ assertThat(windowDecorViewHost.view()).isEqualTo(otherView)
}
@Test
@@ -205,8 +155,7 @@
val t = mock(SurfaceControl.Transaction::class.java)
windowDecorViewHost.release(t)
- verify(windowDecorViewHost.viewHost!!).release()
- verify(t).remove(windowDecorViewHost.surfaceControl)
+ verify(windowDecorViewHost.viewHostAdapter).release(t)
}
private fun CoroutineScope.createDefaultViewHost() =
@@ -214,8 +163,8 @@
context = context,
mainScope = this,
display = context.display,
- surfaceControlViewHostFactory = { c, d, wwm, s ->
- spy(SurfaceControlViewHost(c, d, wwm, s))
- },
+ viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)),
)
+
+ private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view
}
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 0000000..40583f8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/PooledWindowDecorViewHostSupplierTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.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.util.StubTransaction
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+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
+@RunWith(AndroidTestingRunner::class)
+class PooledWindowDecorViewHostSupplierTest : ShellTestCase() {
+
+ private lateinit var supplier: PooledWindowDecorViewHostSupplier
+
+ @Test
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @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) =
+ PooledWindowDecorViewHostSupplier(this, maxPoolSize)
+
+ 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 0000000..245393a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/ReusableWindowDecorViewHostTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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)
+ }
+
+ 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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
new file mode 100644
index 0000000..5109a7c3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/viewhost/SurfaceControlViewHostAdapterTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.SurfaceControlViewHost
+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 org.junit.Assert.assertThrows
+import org.junit.Before
+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 [SurfaceControlViewHostAdapter].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class SurfaceControlViewHostAdapterTest : ShellTestCase() {
+
+ private lateinit var adapter: SurfaceControlViewHostAdapter
+
+ @Before
+ fun setUp() {
+ adapter = SurfaceControlViewHostAdapter(
+ context,
+ context.display,
+ surfaceControlViewHostFactory = { c, d, wwm, s ->
+ spy(SurfaceControlViewHost(c, d, wwm, s))
+ }
+ )
+ }
+
+ @Test
+ fun prepareViewHost() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ assertThat(adapter.viewHost).isNotNull()
+ }
+
+ @Test
+ fun prepareViewHost_alreadyCreated_skips() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ val viewHost = adapter.viewHost!!
+
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ assertThat(adapter.viewHost).isEqualTo(viewHost)
+ }
+
+ @Test
+ fun updateView_layoutInViewHost() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ assertThat(adapter.isInitialized()).isTrue()
+ assertThat(adapter.view()).isEqualTo(view)
+ }
+
+ @Test
+ fun updateView_alreadyLaidOut_relayouts() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val otherParams = WindowManager.LayoutParams(200, 200)
+ adapter.updateView(
+ view = view,
+ attrs = otherParams
+ )
+
+ assertThat(adapter.view()).isEqualTo(view)
+ assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width)
+ }
+
+ @Test
+ fun updateView_replacingView_throws() {
+ val view = View(context)
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = view,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val otherView = View(context)
+ assertThrows(Exception::class.java) {
+ adapter.updateView(
+ view = otherView,
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+ }
+ }
+
+ @Test
+ fun release() {
+ adapter.prepareViewHost(context.resources.configuration, touchableRegion = null)
+ adapter.updateView(
+ view = View(context),
+ attrs = WindowManager.LayoutParams(100, 100)
+ )
+
+ val mockT = mock(SurfaceControl.Transaction::class.java)
+ adapter.release(mockT)
+
+ verify(adapter.viewHost!!).release()
+ verify(mockT).remove(adapter.rootSurface)
+ }
+
+ private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view
+}
\ No newline at end of file
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 5e645cc..a592749 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -38,7 +38,7 @@
}
CursorWindow::~CursorWindow() {
- if (mAshmemFd != -1) {
+ if (mAshmemFd >= 0) {
::munmap(mData, mSize);
::close(mAshmemFd);
} else {
@@ -155,23 +155,27 @@
bool isAshmem;
if (parcel->readBool(&isAshmem)) goto fail;
if (isAshmem) {
- window->mAshmemFd = parcel->readFileDescriptor();
- if (window->mAshmemFd < 0) {
+ int tempFd = parcel->readFileDescriptor();
+ if (tempFd < 0) {
LOG(ERROR) << "Failed readFileDescriptor";
goto fail_silent;
}
- window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0);
- if (window->mAshmemFd < 0) {
+ tempFd = ::fcntl(tempFd, F_DUPFD_CLOEXEC, 0);
+ if (tempFd < 0) {
PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC";
goto fail_silent;
}
- window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0);
+ window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, tempFd, 0);
if (window->mData == MAP_FAILED) {
+ ::close(tempFd);
PLOG(ERROR) << "Failed mmap";
goto fail_silent;
}
+
+ window->mAshmemFd = tempFd;
+
} else {
window->mAshmemFd = -1;
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index de40209..139ccfd 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -16,6 +16,7 @@
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 2540236..0c52169 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 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 @@
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/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index b41f40f..5689df0 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -548,8 +548,45 @@
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 @@
* @see AudioFormat
*/
@FlaggedApi(FLAG_SPEAKER_LAYOUT_API)
+ @SpeakerLayoutChannelMask
public int getSpeakerLayoutChannelMask() {
return mPort.speakerLayoutChannelMask();
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index dd5067a..a3ad340 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -49,6 +49,7 @@
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Describes the properties of a route.
@@ -189,6 +190,7 @@
* the device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BUILTIN_SPEAKER
*/
public static final int TYPE_BUILTIN_SPEAKER = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
@@ -196,6 +198,7 @@
* Indicates the route is a headset, which is the combination of a headphones and a microphone.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADSET
*/
public static final int TYPE_WIRED_HEADSET = AudioDeviceInfo.TYPE_WIRED_HEADSET;
@@ -203,6 +206,7 @@
* Indicates the route is a pair of wired headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_WIRED_HEADPHONES
*/
public static final int TYPE_WIRED_HEADPHONES = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
@@ -210,6 +214,7 @@
* Indicates the route is a bluetooth device, such as a bluetooth speaker or headphones.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLUETOOTH_A2DP
*/
public static final int TYPE_BLUETOOTH_A2DP = AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -217,6 +222,7 @@
* Indicates the route is an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI
*/
public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
@@ -224,6 +230,7 @@
* Indicates the route is an Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_ARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
@@ -232,24 +239,34 @@
* Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HDMI_EARC
*/
@FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
/**
* Indicates the route is a digital line connection (for example S/PDIF).
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_DIGITAL
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_DIGITAL = AudioDeviceInfo.TYPE_LINE_DIGITAL;
/**
* Indicates the route is an analog line-level connection.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_LINE_ANALOG
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_LINE_ANALOG = AudioDeviceInfo.TYPE_LINE_ANALOG;
/**
* Indicates the route is using the auxiliary line-level connectors.
+ *
+ * @see #getType
+ * @see AudioDeviceInfo#TYPE_AUX_LINE
*/
@FlaggedApi(FLAG_ENABLE_NEW_WIRED_MEDIA_ROUTE_2_INFO_TYPES)
public static final int TYPE_AUX_LINE = AudioDeviceInfo.TYPE_AUX_LINE;
@@ -258,6 +275,7 @@
* Indicates the route is a USB audio device.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_DEVICE
*/
public static final int TYPE_USB_DEVICE = AudioDeviceInfo.TYPE_USB_DEVICE;
@@ -265,6 +283,7 @@
* Indicates the route is a USB audio device in accessory mode.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_ACCESSORY
*/
public static final int TYPE_USB_ACCESSORY = AudioDeviceInfo.TYPE_USB_ACCESSORY;
@@ -272,6 +291,7 @@
* Indicates the route is the audio device associated with a dock.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_DOCK
*/
public static final int TYPE_DOCK = AudioDeviceInfo.TYPE_DOCK;
@@ -279,6 +299,7 @@
* Indicates the route is a USB audio headset.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_USB_HEADSET
*/
public static final int TYPE_USB_HEADSET = AudioDeviceInfo.TYPE_USB_HEADSET;
@@ -286,6 +307,7 @@
* Indicates the route is a hearing aid.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_HEARING_AID
*/
public static final int TYPE_HEARING_AID = AudioDeviceInfo.TYPE_HEARING_AID;
@@ -293,6 +315,7 @@
* Indicates the route is a Bluetooth Low Energy (BLE) HEADSET.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_BLE_HEADSET
*/
public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
@@ -304,6 +327,7 @@
* to provide a better experience on multichannel contents.
*
* @see #getType
+ * @see AudioDeviceInfo#TYPE_MULTICHANNEL_GROUP
*/
@FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
@@ -617,7 +641,7 @@
private final String mProviderId;
private final boolean mIsVisibilityRestricted;
private final Set<String> mAllowedPackages;
- private final Set<String> mRequiredPermissions;
+ private final List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private final int mSuitabilityStatus;
MediaRoute2Info(@NonNull Builder builder) {
@@ -642,7 +666,7 @@
mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
mAllowedPackages = builder.mAllowedPackages;
mSuitabilityStatus = builder.mSuitabilityStatus;
- mRequiredPermissions = Set.copyOf(builder.mRequiredPermissions);
+ mRequiredPermissions = List.copyOf(builder.mRequiredPermissions);
}
MediaRoute2Info(@NonNull Parcel in) {
@@ -667,7 +691,12 @@
mProviderId = in.readString();
mIsVisibilityRestricted = in.readBoolean();
mAllowedPackages = Set.of(in.createString8Array());
- mRequiredPermissions = Set.of(in.createString8Array());
+ ArrayList<Set<String>> requiredPermissions = new ArrayList<>();
+ int numRequiredPermissionSets = in.readInt();
+ for (int i = 0; i < numRequiredPermissionSets; i++) {
+ requiredPermissions.add(Set.of(in.createString8Array()));
+ }
+ mRequiredPermissions = List.copyOf(requiredPermissions); // Use copyOf to make it immutable.
mSuitabilityStatus = in.readInt();
}
@@ -911,11 +940,12 @@
}
/**
- * @return the set of permissions which must be held to see this route
+ * @return a list of permission sets - all the permissions in at least one of these sets must be
+ * held to see this route.
*/
@NonNull
@FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
- public Set<String> getRequiredPermissions() {
+ public List<Set<String>> getRequiredPermissions() {
return mRequiredPermissions;
}
@@ -1096,7 +1126,8 @@
.append(", allowedPackages=")
.append(String.join(",", mAllowedPackages))
.append(", mRequiredPermissions=")
- .append(String.join(",", mRequiredPermissions))
+ .append(mRequiredPermissions.stream().map(set -> String.join(",", set)).collect(
+ Collectors.joining("),(", "(", ")")))
.append(", suitabilityStatus=")
.append(mSuitabilityStatus)
.append(" }")
@@ -1130,7 +1161,10 @@
dest.writeString(mProviderId);
dest.writeBoolean(mIsVisibilityRestricted);
dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
- dest.writeString8Array(mRequiredPermissions.toArray(new String[0]));
+ dest.writeInt(mRequiredPermissions.size());
+ for (Set<String> permissionSet : mRequiredPermissions) {
+ dest.writeString8Array(permissionSet.toArray(new String[0]));
+ }
dest.writeInt(mSuitabilityStatus);
}
@@ -1279,7 +1313,7 @@
private String mProviderId;
private boolean mIsVisibilityRestricted;
private Set<String> mAllowedPackages;
- private Set<String> mRequiredPermissions;
+ private List<Set<String>> mRequiredPermissions;
@SuitabilityStatus private int mSuitabilityStatus;
/**
@@ -1305,7 +1339,7 @@
mDeduplicationIds = Set.of();
mAllowedPackages = Set.of();
mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
- mRequiredPermissions = Set.of();
+ mRequiredPermissions = List.of();
}
/**
@@ -1587,7 +1621,7 @@
public Builder setVisibilityPublic() {
mIsVisibilityRestricted = false;
mAllowedPackages = Set.of();
- mRequiredPermissions = Set.of();
+ mRequiredPermissions = List.of();
return this;
}
@@ -1614,13 +1648,31 @@
/**
* Limits the visibility of this route to holders of a set of permissions.
*
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
* @param requiredPermissions the list of all permissions which must be held in order to
* see this route.
*/
@NonNull
@FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
public Builder setRequiredPermissions(@NonNull Set<String> requiredPermissions) {
- mRequiredPermissions = Set.copyOf(requiredPermissions);
+ return setRequiredPermissions(List.of(requiredPermissions));
+ }
+
+ /**
+ * Limits the visibility of this route to holders of one of a set of permissions.
+ *
+ * <p>Calls to this method override any previous calls of
+ * {@link #setRequiredPermissions(Set)} or {@link #setRequiredPermissions(List)}.
+ *
+ * @param requiresOneOf a list of Sets of permissions. Holding all permissions in at least
+ * one of the Sets is required for the route to be visible.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_ENABLE_ROUTE_VISIBILITY_CONTROL_API)
+ public Builder setRequiredPermissions(@NonNull List<Set<String>> requiresOneOf) {
+ mRequiredPermissions = List.copyOf(requiresOneOf);
return this;
}
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/media/java/android/media/quality/ActiveProcessingPicture.aidl
similarity index 80%
rename from core/java/android/app/ondeviceintelligence/Feature.aidl
rename to media/java/android/media/quality/ActiveProcessingPicture.aidl
index 18494d7..2851306 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.aidl
+++ b/media/java/android/media/quality/ActiveProcessingPicture.aidl
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package android.media.quality;
-/**
- * @hide
- */
-parcelable Feature;
+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 0000000..e16ad62
--- /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 70211ca..253c2d8 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -23,51 +23,58 @@
import android.media.quality.ParamCapability;
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);
- 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);
+ 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 34aa2b0..7071a16 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -29,5 +29,5 @@
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 9043757..30bb106 100644
--- a/media/java/android/media/quality/ISoundProfileCallback.aidl
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -29,5 +29,5 @@
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 7b0bd04..6a52bcb 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -75,11 +75,9 @@
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 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 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 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 9d66086..7e87462 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -20,11 +20,13 @@
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 @@
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 @@
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 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 @@
}
}
@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 @@
}
}
@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 @@
}
}
-
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
@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 @@
/**
* Gets all package names whose picture profiles are available.
*
- * @see #getPictureProfilesByPackage(String)
+ * @see #getPictureProfilesByPackage(String, boolean)
* @hide
*/
@SystemApi
@@ -285,7 +302,7 @@
@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();
}
@@ -295,9 +312,21 @@
* Gets picture profile handle by profile ID.
* @hide
*/
- public PictureProfileHandle getPictureProfileHandle(String id) {
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id) {
try {
- return mService.getPictureProfileHandle(id, mUserId);
+ return mService.getPictureProfileHandle(id, mUserHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets sound profile handle by profile ID.
+ * @hide
+ */
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id) {
+ try {
+ return mService.getSoundProfileHandle(id, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -308,10 +337,12 @@
*
* <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();
}
@@ -320,10 +351,13 @@
/**
* 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();
}
@@ -332,10 +366,12 @@
/**
* 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();
}
@@ -371,18 +407,20 @@
}
}
-
/**
* 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();
}
@@ -392,14 +430,18 @@
/**
* 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();
}
@@ -407,11 +449,16 @@
/**
* 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();
}
@@ -429,7 +476,7 @@
@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();
}
@@ -438,7 +485,7 @@
/**
* Gets all package names whose sound profiles are available.
*
- * @see #getSoundProfilesByPackage(String)
+ * @see #getSoundProfilesByPackage(String, boolean)
*
* @hide
*/
@@ -447,7 +494,7 @@
@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();
}
@@ -459,10 +506,12 @@
*
* <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();
}
@@ -471,10 +520,13 @@
/**
* 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();
}
@@ -483,10 +535,12 @@
/**
* 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();
}
@@ -498,7 +552,7 @@
@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();
}
@@ -516,7 +570,7 @@
@NonNull
public List<String> getPictureProfileAllowList() {
try {
- return mService.getPictureProfileAllowList(mUserId);
+ return mService.getPictureProfileAllowList(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -530,7 +584,7 @@
@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();
}
@@ -548,7 +602,7 @@
@NonNull
public List<String> getSoundProfileAllowList() {
try {
- return mService.getSoundProfileAllowList(mUserId);
+ return mService.getSoundProfileAllowList(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -562,7 +616,7 @@
@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();
}
@@ -574,7 +628,7 @@
*/
public boolean isSupported() {
try {
- return mService.isSupported(mUserId);
+ return mService.isSupported(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -592,7 +646,7 @@
@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();
}
@@ -603,7 +657,7 @@
*/
public boolean isAutoPictureQualityEnabled() {
try {
- return mService.isAutoPictureQualityEnabled(mUserId);
+ return mService.isAutoPictureQualityEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -620,7 +674,7 @@
@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();
}
@@ -631,7 +685,7 @@
*/
public boolean isSuperResolutionEnabled() {
try {
- return mService.isSuperResolutionEnabled(mUserId);
+ return mService.isSuperResolutionEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -649,7 +703,7 @@
@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();
}
@@ -660,7 +714,7 @@
*/
public boolean isAutoSoundQualityEnabled() {
try {
- return mService.isAutoSoundQualityEnabled(mUserId);
+ return mService.isAutoSoundQualityEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -709,7 +763,7 @@
@NonNull AmbientBacklightSettings settings) {
Preconditions.checkNotNull(settings);
try {
- mService.setAmbientBacklightSettings(settings, mUserId);
+ mService.setAmbientBacklightSettings(settings, mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -720,7 +774,7 @@
*/
public boolean isAmbientBacklightEnabled() {
try {
- return mService.isAmbientBacklightEnabled(mUserId);
+ return mService.isAmbientBacklightEnabled(mUserHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -734,7 +788,7 @@
@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();
}
@@ -791,11 +845,11 @@
});
}
- 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);
}
});
}
@@ -851,11 +905,11 @@
});
}
- 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);
}
});
}
@@ -921,9 +975,11 @@
/**
* 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) {
}
/**
@@ -976,9 +1032,11 @@
/**
* 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) {
}
/**
@@ -1004,4 +1062,86 @@
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/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index dcb4222..6064485 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -48,6 +48,7 @@
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final PictureProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -121,6 +122,7 @@
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(PictureProfileHandle.class.getClassLoader());
}
@Override
@@ -131,6 +133,7 @@
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -163,13 +166,15 @@
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull PictureProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -251,6 +256,15 @@
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public PictureProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link PictureProfile}.
*/
public static final class Builder {
@@ -265,6 +279,7 @@
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private PictureProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -283,6 +298,7 @@
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -350,6 +366,16 @@
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull PictureProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -361,7 +387,8 @@
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/media/java/android/media/quality/PictureProfileHandle.java b/media/java/android/media/quality/PictureProfileHandle.java
index 714fd36..d9d2193 100644
--- a/media/java/android/media/quality/PictureProfileHandle.java
+++ b/media/java/android/media/quality/PictureProfileHandle.java
@@ -28,11 +28,14 @@
* 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 @@
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/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
index c7fb4dd..1dd59ab 100644
--- a/media/java/android/media/quality/SoundProfile.java
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -48,6 +48,7 @@
private final String mPackageName;
@NonNull
private final PersistableBundle mParams;
+ private final SoundProfileHandle mHandle;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -120,6 +121,7 @@
mInputId = in.readString();
mPackageName = in.readString();
mParams = in.readPersistableBundle();
+ mHandle = in.readParcelable(SoundProfileHandle.class.getClassLoader());
}
@Override
@@ -130,6 +132,7 @@
dest.writeString(mInputId);
dest.writeString(mPackageName);
dest.writePersistableBundle(mParams);
+ dest.writeParcelable(mHandle, flags);
}
@Override
@@ -162,13 +165,15 @@
@NonNull String name,
@Nullable String inputId,
@NonNull String packageName,
- @NonNull PersistableBundle params) {
+ @NonNull PersistableBundle params,
+ @NonNull SoundProfileHandle handle) {
this.mId = id;
this.mType = type;
this.mName = name;
this.mInputId = inputId;
this.mPackageName = packageName;
this.mParams = params;
+ this.mHandle = handle;
}
/**
@@ -250,6 +255,15 @@
}
/**
+ * Gets profile handle
+ * @hide
+ */
+ @NonNull
+ public SoundProfileHandle getHandle() {
+ return mHandle;
+ }
+
+ /**
* A builder for {@link SoundProfile}
*/
public static final class Builder {
@@ -264,6 +278,7 @@
private String mPackageName;
@NonNull
private PersistableBundle mParams;
+ private SoundProfileHandle mHandle;
/**
* Creates a new Builder.
@@ -282,6 +297,7 @@
mPackageName = p.getPackageName();
mInputId = p.getInputId();
mParams = p.getParameters();
+ mHandle = p.getHandle();
}
/**
@@ -349,6 +365,16 @@
}
/**
+ * Sets profile handle.
+ * @hide
+ */
+ @NonNull
+ public Builder setHandle(@NonNull SoundProfileHandle handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ /**
* Builds the instance.
*/
@NonNull
@@ -360,7 +386,8 @@
mName,
mInputId,
mPackageName,
- mParams);
+ mParams,
+ mHandle);
return o;
}
}
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/media/java/android/media/quality/SoundProfileHandle.aidl
similarity index 80%
copy from core/java/android/app/ondeviceintelligence/Feature.aidl
copy to media/java/android/media/quality/SoundProfileHandle.aidl
index 18494d7..6b8161c 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.aidl
+++ b/media/java/android/media/quality/SoundProfileHandle.aidl
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package android.media.quality;
-/**
- * @hide
- */
-parcelable Feature;
+parcelable SoundProfileHandle;
diff --git a/media/java/android/media/quality/SoundProfileHandle.java b/media/java/android/media/quality/SoundProfileHandle.java
new file mode 100644
index 0000000..edb546e
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfileHandle.java
@@ -0,0 +1,72 @@
+/*
+ * 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.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A type-safe handle to a sound profile.
+ *
+ * @hide
+ */
+public final class SoundProfileHandle implements Parcelable {
+ public static final @NonNull SoundProfileHandle NONE = new SoundProfileHandle(-1000);
+
+ private final long mId;
+
+ /** @hide */
+ public SoundProfileHandle(long id) {
+ mId = id;
+ }
+
+ /** @hide */
+ public long getId() {
+ return mId;
+ }
+
+ /** @hide */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mId);
+ }
+
+ /** @hide */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ public static final @NonNull Creator<SoundProfileHandle> CREATOR =
+ new Creator<SoundProfileHandle>() {
+ @Override
+ public SoundProfileHandle createFromParcel(Parcel in) {
+ return new SoundProfileHandle(in);
+ }
+
+ @Override
+ public SoundProfileHandle[] newArray(int size) {
+ return new SoundProfileHandle[size];
+ }
+ };
+
+ private SoundProfileHandle(@NonNull Parcel in) {
+ mId = in.readLong();
+ }
+}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index cd6de5a..129d616 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -73,6 +73,7 @@
"surface_control.cpp",
"surface_texture.cpp",
"system_fonts.cpp",
+ "system_health.cpp",
"trace.cpp",
"thermal.cpp",
],
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
index 5322136..0749731 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 @@
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 @@
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 @@
}
return result;
-}
\ No newline at end of file
+}
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 78a7ddb..1ccadf9 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -320,6 +320,23 @@
ASystemFontIterator_open; # introduced=29
ASystemFontIterator_close; # introduced=29
ASystemFontIterator_next; # introduced=29
+ ASystemHealth_getCpuHeadroom; # introduced=36
+ ASystemHealth_getGpuHeadroom; # introduced=36
+ ASystemHealth_getCpuHeadroomMinIntervalMillis; # introduced=36
+ ASystemHealth_getGpuHeadroomMinIntervalMillis; # introduced=36
+ ACpuHeadroomParams_create; # introduced=36
+ ACpuHeadroomParams_destroy; # introduced=36
+ ACpuHeadroomParams_setCalculationType; # introduced=36
+ ACpuHeadroomParams_getCalculationType; # introduced=36
+ ACpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_getCalculationWindowMillis; # introduced=36
+ ACpuHeadroomParams_setTids; # introduced=36
+ AGpuHeadroomParams_create; # introduced=36
+ AGpuHeadroomParams_destroy; # introduced=36
+ AGpuHeadroomParams_setCalculationType; # introduced=36
+ AGpuHeadroomParams_getCalculationType; # introduced=36
+ AGpuHeadroomParams_setCalculationWindowMillis; # introduced=36
+ AGpuHeadroomParams_getCalculationWindowMillis; # introduced=36
AFont_close; # introduced=29
AFont_getFontFilePath; # introduced=29
AFont_getWeight; # introduced=29
@@ -376,6 +393,7 @@
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 608c01c..1945d90 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -214,6 +214,7 @@
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 @@
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 @@
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 @@
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/system_health.cpp b/native/android/system_health.cpp
new file mode 100644
index 0000000..f3fa9f6
--- /dev/null
+++ b/native/android/system_health.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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.
+ */
+
+#include <aidl/android/hardware/power/CpuHeadroomParams.h>
+#include <aidl/android/hardware/power/GpuHeadroomParams.h>
+#include <aidl/android/os/CpuHeadroomParamsInternal.h>
+#include <aidl/android/os/GpuHeadroomParamsInternal.h>
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/system_health.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+
+using namespace android;
+using namespace aidl::android::os;
+namespace hal = aidl::android::hardware::power;
+
+struct ACpuHeadroomParams : public CpuHeadroomParamsInternal {};
+struct AGpuHeadroomParams : public GpuHeadroomParamsInternal {};
+
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+const int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+const int CPU_HEADROOM_MAX_TID_COUNT = 5;
+
+struct ASystemHealthManager {
+public:
+ static ASystemHealthManager* getInstance();
+ ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager);
+ ASystemHealthManager() = delete;
+ ~ASystemHealthManager();
+ int getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom);
+ int getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom);
+ int getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+ int getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis);
+
+private:
+ static ASystemHealthManager* create(std::shared_ptr<IHintManager> hintManager);
+ std::shared_ptr<IHintManager> mHintManager;
+};
+
+ASystemHealthManager* ASystemHealthManager::getInstance() {
+ static std::once_flag creationFlag;
+ static ASystemHealthManager* instance = nullptr;
+ std::call_once(creationFlag, []() { instance = create(nullptr); });
+ return instance;
+}
+
+ASystemHealthManager::ASystemHealthManager(std::shared_ptr<IHintManager>& hintManager)
+ : mHintManager(std::move(hintManager)) {}
+
+ASystemHealthManager::~ASystemHealthManager() {}
+
+ASystemHealthManager* ASystemHealthManager::create(std::shared_ptr<IHintManager> hintManager) {
+ if (!hintManager) {
+ hintManager = IHintManager::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService("performance_hint")));
+ }
+ if (hintManager == nullptr) {
+ ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+ return nullptr;
+ }
+ return new ASystemHealthManager(hintManager);
+}
+
+ASystemHealthManager* ASystemHealth_acquireManager() {
+ return ASystemHealthManager::getInstance();
+}
+
+int ASystemHealthManager::getCpuHeadroom(const ACpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::CpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ CpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getCpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getCpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid ACpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getCpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ } else if (ret.getExceptionCode() == EX_SECURITY) {
+ return EPERM;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroom(const AGpuHeadroomParams* params, float* outHeadroom) {
+ std::optional<hal::GpuHeadroomResult> res;
+ ::ndk::ScopedAStatus ret;
+ GpuHeadroomParamsInternal internalParams;
+ if (!params) {
+ ret = mHintManager->getGpuHeadroom(internalParams, &res);
+ } else {
+ ret = mHintManager->getGpuHeadroom(*params, &res);
+ }
+ if (!ret.isOk()) {
+ LOG_ALWAYS_FATAL_IF(ret.getExceptionCode() == EX_ILLEGAL_ARGUMENT,
+ "Invalid AGpuHeadroomParams: %s", ret.getMessage());
+ ALOGE("ASystemHealth_getGpuHeadroom fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+ return OK;
+}
+
+int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
+ int64_t minIntervalMillis = 0;
+ ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
+ if (!ret.isOk()) {
+ ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
+ if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return ENOTSUP;
+ }
+ return EPIPE;
+ }
+ *outMinIntervalMillis = minIntervalMillis;
+ return OK;
+}
+
+int ASystemHealth_getCpuHeadroom(const ACpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getGpuHeadroom(const AGpuHeadroomParams* _Nullable params,
+ float* _Nonnull outHeadroom) {
+ LOG_ALWAYS_FATAL_IF(outHeadroom == nullptr, "%s: outHeadroom should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroom(params, outHeadroom);
+}
+
+int ASystemHealth_getCpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getCpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+int ASystemHealth_getGpuHeadroomMinIntervalMillis(int64_t* _Nonnull outMinIntervalMillis) {
+ LOG_ALWAYS_FATAL_IF(outMinIntervalMillis == nullptr,
+ "%s: outMinIntervalMillis should not be null", __FUNCTION__);
+ auto manager = ASystemHealthManager::getInstance();
+ if (manager == nullptr) return ENOTSUP;
+ return manager->getGpuHeadroomMinIntervalMillis(outMinIntervalMillis);
+}
+
+void ACpuHeadroomParams_setCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+void AGpuHeadroomParams_setCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params,
+ int windowMillis) {
+ LOG_ALWAYS_FATAL_IF(windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN ||
+ windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX,
+ "%s: windowMillis should be in range [50, 10000] but got %d", __FUNCTION__,
+ windowMillis);
+ params->calculationWindowMillis = windowMillis;
+}
+
+int ACpuHeadroomParams_getCalculationWindowMillis(ACpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+int AGpuHeadroomParams_getCalculationWindowMillis(AGpuHeadroomParams* _Nonnull params) {
+ return params->calculationWindowMillis;
+}
+
+void ACpuHeadroomParams_setTids(ACpuHeadroomParams* _Nonnull params, const int* _Nonnull tids,
+ int tidsSize) {
+ LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
+ LOG_ALWAYS_FATAL_IF(tidsSize > CPU_HEADROOM_MAX_TID_COUNT, "%s: tids size should not exceed 5",
+ __FUNCTION__);
+ params->tids.resize(tidsSize);
+ params->tids.clear();
+ for (int i = 0; i < tidsSize; ++i) {
+ LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
+ tids[i]);
+ params->tids[i] = tids[i];
+ }
+}
+
+void ACpuHeadroomParams_setCalculationType(ACpuHeadroomParams* _Nonnull params,
+ ACpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > ACpuHeadroomCalculationType::
+ ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of ACpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::CpuHeadroomParams::CalculationType>(calculationType);
+}
+
+ACpuHeadroomCalculationType ACpuHeadroomParams_getCalculationType(
+ ACpuHeadroomParams* _Nonnull params) {
+ return static_cast<ACpuHeadroomCalculationType>(params->calculationType);
+}
+
+void AGpuHeadroomParams_setCalculationType(AGpuHeadroomParams* _Nonnull params,
+ AGpuHeadroomCalculationType calculationType) {
+ LOG_ALWAYS_FATAL_IF(calculationType < AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_MIN ||
+ calculationType > AGpuHeadroomCalculationType::
+ AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE,
+ "%s: calculationType should be one of AGpuHeadroomCalculationType values "
+ "but got %d",
+ __FUNCTION__, calculationType);
+ params->calculationType = static_cast<hal::GpuHeadroomParams::CalculationType>(calculationType);
+}
+
+AGpuHeadroomCalculationType AGpuHeadroomParams_getCalculationType(
+ AGpuHeadroomParams* _Nonnull params) {
+ return static_cast<AGpuHeadroomCalculationType>(params->calculationType);
+}
+
+ACpuHeadroomParams* _Nonnull ACpuHeadroomParams_create() {
+ return new ACpuHeadroomParams();
+}
+
+AGpuHeadroomParams* _Nonnull AGpuHeadroomParams_create() {
+ return new AGpuHeadroomParams();
+}
+
+void ACpuHeadroomParams_destroy(ACpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
+
+void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nonnull params) {
+ delete params;
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index b8f574f..c166e73 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -299,6 +299,10 @@
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/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
index dd7b03d..67b496e 100644
--- a/nfc/lint-baseline.xml
+++ b/nfc/lint-baseline.xml
@@ -2,215 +2,6 @@
<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
<issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
- errorLine1=" AidGroup aidGroup = new AidGroup(aids, category);"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="377"
- column="29"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
- errorLine1=" return (group != null ? group.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="537"
- column="43"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
- errorLine1=" return (group != null ? group.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="547"
- column="47"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="714"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
- errorLine2=" ~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="724"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
- errorLine1=" if (!serviceInfo.isOnHost()) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="755"
- column="34"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="756"
- column="40"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=' "OffHost" : serviceInfo.getOffHostSecureElement();'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="757"
- column="53"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
- errorLine1=" if (!serviceInfo.isOnHost()) {"
- errorLine2=" ~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="772"
- column="38"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="773"
- column="44"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
- errorLine1=' "Offhost" : serviceInfo.getOffHostSecureElement();'
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="774"
- column="57"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="798"
- column="55"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
- errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
- errorLine2=" ~~~~~~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="808"
- column="59"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="1032"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
- line="1066"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" resumed = activity.isResumed();"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
- line="124"
- column="32"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
- line="2457"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
- line="315"
- column="23"/>
- </issue>
-
- <issue
- id="NewApi"
- message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
- errorLine1=" if (!activity.isResumed()) {"
- errorLine2=" ~~~~~~~~~">
- <location
- file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
- line="351"
- column="23"/>
- </issue>
-
- <issue
id="FlaggedApi"
message="Method `NfcOemExtension()` is a flagged API and should be inside an `if (Flags.nfcOemExtension())` check (or annotate the surrounding method `NfcAdapter` with `@FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) to transfer requirement to caller`)"
errorLine1=" mNfcOemExtension = new NfcOemExtension(mContext, this);"
@@ -287,4 +78,4 @@
column="44"/>
</issue>
-</issues>
\ No newline at end of file
+</issues>
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 2ba93f1..560e751 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -91,6 +92,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -336,20 +338,27 @@
/**
* Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
* this observer if it does not already exist.
+ * For executing mitigations observers will receive callback on the given executor.
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
* it will resume observing any packages requested from a previous boot.
- * @hide
+ *
+ * @param observer instance of {@link PackageHealthObserver} for observing package failures
+ * and boot loops.
+ * @param executor Executor for the thread on which observers would receive callbacks
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(@NonNull PackageHealthObserver observer,
+ @NonNull @CallbackExecutor Executor executor) {
synchronized (sLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
} else {
internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
new ArrayList<>());
internalObserver.registeredObserver = observer;
+ internalObserver.observerExecutor = executor;
mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
syncState("added new observer");
}
@@ -357,40 +366,53 @@
}
/**
- * Starts observing the health of the {@code packages} for {@code observer} and notifies
- * {@code observer} of any package failures within the monitoring duration.
+ * Starts observing the health of the {@code packages} for {@code observer}.
+ * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
+ * this API.
*
* <p>If monitoring a package supporting explicit health check, at the end of the monitoring
* duration if {@link #onHealthCheckPassed} was never called,
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the package failed.
+ * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
+ * package failed.
*
* <p>If {@code observer} is already monitoring a package in {@code packageNames},
* the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default depending on if the package is contained in
- * {@link mPackagesWithExplicitHealthCheckEnabled}.
+ * check state will be reset to a default.
*
- * <p>If {@code packageNames} is empty, this will be a no-op.
+ * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
+ * calling this method.
*
- * <p>If {@code durationMs} is less than 1, a default monitoring duration
- * {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
- * @hide
+ * @param packageNames The list of packages to check. If this is empty, the call will be a
+ * no-op.
+ *
+ * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
+ * less than 1, a default monitoring duration 2 days will be used.
+ *
+ * @throws IllegalStateException if the observer was not previously registered
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
- long durationMs) {
+ public void startExplicitHealthCheck(@NonNull PackageHealthObserver observer,
+ @NonNull List<String> packageNames, long timeoutMs) {
+ synchronized (sLock) {
+ if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
+ Slog.wtf(TAG, "No observer found, need to register the observer: "
+ + observer.getUniqueIdentifier());
+ throw new IllegalStateException("Observer not registered");
+ }
+ }
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
return;
}
- if (durationMs < 1) {
- Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
+ if (timeoutMs < 1) {
+ Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
+ observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
- durationMs = DEFAULT_OBSERVING_DURATION_MS;
+ timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
}
List<MonitoredPackage> packages = new ArrayList<>();
for (int i = 0; i < packageNames.size(); i++) {
// Health checks not available yet so health check state will start INACTIVE
- MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), durationMs, false);
+ MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
if (pkg != null) {
packages.add(pkg);
} else {
@@ -423,9 +445,6 @@
}
}
- // Register observer in case not already registered
- registerHealthObserver(observer);
-
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
syncState("updated observers");
@@ -437,9 +456,8 @@
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
* even from a previous boot.
- * @hide
*/
- public void unregisterHealthObserver(PackageHealthObserver observer) {
+ public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
synchronized (sLock) {
mAllObservers.remove(observer.getUniqueIdentifier());
@@ -485,7 +503,7 @@
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
VersionedPackage versionedPackage = packages.get(pIndex);
// Observer that will receive failure for versionedPackage
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
MonitoredPackage currentMonitoredPackage = null;
@@ -506,7 +524,7 @@
versionedPackage, failureReason, mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
currentMonitoredPackage = p;
}
@@ -515,18 +533,23 @@
// Execute action with least user impact
if (currentObserverToNotify != null) {
- int mitigationCount = 1;
+ int mitigationCount;
if (currentMonitoredPackage != null) {
currentMonitoredPackage.noteMitigationCallLocked();
mitigationCount =
currentMonitoredPackage.getMitigationCountLocked();
+ } else {
+ mitigationCount = 1;
}
if (Flags.recoverabilityDetection()) {
maybeExecute(currentObserverToNotify, versionedPackage,
failureReason, currentObserverImpact, mitigationCount);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(
- versionedPackage, failureReason, mitigationCount);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(
+ versionedPackage, failureReason, mitigationCount));
}
}
}
@@ -539,10 +562,11 @@
* For native crashes or explicit health check failures, call directly into each observer to
* mitigate the error without going through failure threshold logic.
*/
+ @GuardedBy("sLock")
private void handleFailureImmediately(List<VersionedPackage> packages,
@FailureReasons int failureReason) {
VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
- PackageHealthObserver currentObserverToNotify = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (ObserverInternal observer: mAllObservers.values()) {
PackageHealthObserver registeredObserver = observer.registeredObserver;
@@ -551,7 +575,7 @@
failingPackage, failureReason, 1);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
@@ -561,23 +585,30 @@
maybeExecute(currentObserverToNotify, failingPackage, failureReason,
currentObserverImpact, /*mitigationCount=*/ 1);
} else {
- currentObserverToNotify.onExecuteHealthCheckMitigation(failingPackage,
- failureReason, 1);
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
+ failureReason, 1));
+
}
}
}
- private void maybeExecute(PackageHealthObserver currentObserverToNotify,
+ private void maybeExecute(ObserverInternal currentObserverToNotify,
VersionedPackage versionedPackage,
@FailureReasons int failureReason,
int currentObserverImpact,
int mitigationCount) {
if (allowMitigations(currentObserverImpact, versionedPackage)) {
+ PackageHealthObserver registeredObserver;
synchronized (sLock) {
mLastMitigation = mSystemClock.uptimeMillis();
+ registeredObserver = currentObserverToNotify.registeredObserver;
}
- currentObserverToNotify.onExecuteHealthCheckMitigation(versionedPackage, failureReason,
- mitigationCount);
+ currentObserverToNotify.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
+ failureReason, mitigationCount));
}
}
@@ -613,8 +644,7 @@
mBootThreshold.reset();
}
int mitigationCount = mBootThreshold.getMitigationCount() + 1;
- PackageHealthObserver currentObserverToNotify = null;
- ObserverInternal currentObserverInternal = null;
+ ObserverInternal currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
@@ -626,25 +656,31 @@
: registeredObserver.onBootLoop(mitigationCount);
if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
- currentObserverToNotify = registeredObserver;
- currentObserverInternal = observer;
+ currentObserverToNotify = observer;
currentObserverImpact = impact;
}
}
}
+
if (currentObserverToNotify != null) {
+ PackageHealthObserver registeredObserver =
+ currentObserverToNotify.registeredObserver;
if (Flags.recoverabilityDetection()) {
int currentObserverMitigationCount =
- currentObserverInternal.getBootMitigationCount() + 1;
- currentObserverInternal.setBootMitigationCount(
+ currentObserverToNotify.getBootMitigationCount() + 1;
+ currentObserverToNotify.setBootMitigationCount(
currentObserverMitigationCount);
saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.onExecuteBootLoopMitigation(
- currentObserverMitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ currentObserverMitigationCount));
} else {
mBootThreshold.setMitigationCount(mitigationCount);
mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.onExecuteBootLoopMitigation(mitigationCount);
+ currentObserverToNotify.observerExecutor
+ .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
+ mitigationCount));
+
}
}
}
@@ -879,7 +915,7 @@
* passed to observers in these API.
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
@@ -1136,8 +1172,10 @@
if (versionedPkg != null) {
Slog.i(TAG,
"Explicit health check failed for package " + versionedPkg);
- registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK, 1);
+ observer.observerExecutor.execute(() ->
+ registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
+ PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
+ 1));
}
}
}
@@ -1395,6 +1433,7 @@
@Nullable
@GuardedBy("sLock")
public PackageHealthObserver registeredObserver;
+ public Executor observerExecutor;
private int mMitigationCount;
ObserverInternal(String name, List<MonitoredPackage> packages) {
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
index 992f581..bad6ab7 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
@@ -160,7 +160,7 @@
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), context.getMainExecutor());
}
private static boolean isDisabled() {
@@ -313,7 +313,7 @@
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 311def8..c80a1a4 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -111,7 +111,8 @@
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this,
+ context.getMainExecutor());
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -273,16 +274,6 @@
Preconditions.checkState(mHandler.getLooper().isCurrentThread());
}
- /**
- * Start observing health of {@code packages} for {@code durationMs}.
- * This may cause {@code packages} to be rolled back if they crash too freqeuntly.
- */
- @AnyThread
- @NonNull
- public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
- }
-
@AnyThread
@NonNull
public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 88fe36c..4fea937 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -87,6 +87,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -362,7 +363,7 @@
* it will resume observing any packages requested from a previous boot.
* @hide
*/
- public void registerHealthObserver(PackageHealthObserver observer) {
+ public void registerHealthObserver(PackageHealthObserver observer, Executor ignoredExecutor) {
synchronized (mLock) {
ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
@@ -396,7 +397,7 @@
* {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
* @hide
*/
- public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
+ public void startExplicitHealthCheck(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
if (packageNames.isEmpty()) {
Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
@@ -445,7 +446,7 @@
}
// Register observer in case not already registered
- registerHealthObserver(observer);
+ registerHealthObserver(observer, null);
// Sync after we add the new packages to the observers. We may have received packges
// requiring an earlier schedule than we are currently scheduled for.
@@ -861,7 +862,7 @@
* otherwise
*
* <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startObservingHealth}.
+ * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
*/
default boolean mayObservePackage(@NonNull String packageName) {
return false;
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
index f757236..2bb72fb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/RescueParty.java
@@ -166,7 +166,7 @@
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
PackageWatchdog.getInstance(context).registerHealthObserver(
- RescuePartyObserver.getInstance(context));
+ RescuePartyObserver.getInstance(context), null);
}
private static boolean isDisabled() {
@@ -387,7 +387,7 @@
callingPackageList.addAll(callingPackages);
Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
+ updatedNamespace);
- PackageWatchdog.getInstance(context).startObservingHealth(
+ PackageWatchdog.getInstance(context).startExplicitHealthCheck(
rescuePartyObserver,
callingPackageList,
DEFAULT_OBSERVING_DURATION_MS);
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7445534..0692cdb 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -110,7 +110,7 @@
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ PackageWatchdog.getInstance(mContext).registerHealthObserver(this, null);
mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
@@ -284,7 +284,7 @@
@AnyThread
@NonNull
public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
- PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
+ PackageWatchdog.getInstance(mContext).startExplicitHealthCheck(this, packages, durationMs);
}
@AnyThread
diff --git a/packages/NeuralNetworks/framework/Android.bp b/packages/NeuralNetworks/framework/Android.bp
new file mode 100644
index 0000000..6f45daa
--- /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
similarity index 96%
rename from core/java/android/app/ondeviceintelligence/DownloadCallback.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/DownloadCallback.java
index 30c6e19..95fb288 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.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 @@
/**
* 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 @@
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/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
similarity index 93%
rename from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
index 0589bf8..47cfb4a 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.aidl
@@ -19,4 +19,4 @@
/**
* @hide
*/
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
similarity index 96%
rename from core/java/android/app/ondeviceintelligence/Feature.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/Feature.java
index bcc56073..88f4de29 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.Parcelable;
import android.os.PersistableBundle;
+import java.util.Objects;
+
/**
* Represents a typical feature associated with on-device intelligence.
*
@@ -56,9 +58,8 @@
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 @@
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 @@
/**
* 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
similarity index 92%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
index 0589bf8..c5b3532 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -19,4 +19,4 @@
/**
* @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
similarity index 87%
rename from core/java/android/app/ondeviceintelligence/FeatureDetails.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
index 44930f2..063cfb8 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -19,18 +19,18 @@
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 @@
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 @@
@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 @@
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/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
similarity index 82%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
index 0589bf8..1fe201f 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ICancellationSignal.aidl
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -17,6 +17,8 @@
package android.app.ondeviceintelligence;
/**
- * @hide
- */
-parcelable FeatureDetails;
+ * @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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
rename to 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
similarity index 97%
rename from core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index 1977a39..fac5ec6 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
rename to 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 0000000..6f07693
--- /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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
similarity index 92%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
index 0589bf8..6f63254 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.aidl
@@ -19,4 +19,4 @@
/**
* @hide
*/
-parcelable FeatureDetails;
+@JavaOnlyStableParcelable parcelable InferenceInfo;
diff --git a/core/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/InferenceInfo.java
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/InferenceInfo.java
rename to 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
similarity index 97%
rename from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
index 03ff563a..2881c9d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceException.java
@@ -20,13 +20,14 @@
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 @@
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 0000000..7d35dd7
--- /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
similarity index 88%
rename from core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 91651e3..dc0665a 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.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.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 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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
@Override
public void onSuccess(@InferenceParams Bundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onResult(result));
});
@@ -464,7 +491,7 @@
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onError(
new OnDeviceIntelligenceException(
@@ -476,7 +503,7 @@
@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 @@
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 @@
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 @@
}, 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ProcessingCallback.java
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/ProcessingSignal.java
rename to 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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/StreamingProcessingCallback.java
rename to 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
similarity index 93%
rename from core/java/android/app/ondeviceintelligence/TokenInfo.aidl
rename to packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
index 2c19c1e..599b337 100644
--- a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
+++ b/packages/NeuralNetworks/framework/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -19,4 +19,4 @@
/**
* @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
similarity index 100%
rename from core/java/android/app/ondeviceintelligence/TokenInfo.java
rename to 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 0000000..2916f03
--- /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
similarity index 95%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index 45c4350..cba18c1 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -18,14 +18,14 @@
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
similarity index 93%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index 1af3b0f..504fdd9 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.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
similarity index 100%
rename from core/java/android/service/ondeviceintelligence/IProcessingUpdateStatusCallback.aidl
rename to 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
similarity index 100%
rename from core/java/android/service/ondeviceintelligence/IRemoteProcessingService.aidl
rename to 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
similarity index 95%
rename from core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
index a6f49e1..253df89 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.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
similarity index 67%
rename from core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index d82fe1c..6907e2b 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -18,8 +18,6 @@
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.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.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.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 @@
@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 @@
@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 @@
/** {@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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
}
/**
+ * 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 @@
new IProcessingUpdateStatusCallback.Stub() {
@Override
public void onSuccess(PersistableBundle result) {
- Binder.withCleanCallingIdentity(() -> {
+ BinderUtils.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> statusReceiver.onResult(result));
});
@@ -314,7 +430,7 @@
@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 @@
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 @@
} 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
similarity index 74%
rename from core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
rename to packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 3181556..315dbaf 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
+++ b/packages/NeuralNetworks/framework/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -19,10 +19,8 @@
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.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.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.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 @@
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 @@
* @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 @@
@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;
+ }
+ }
+ };
}
- /**
- * @hide
- */
+ // 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;
+ }
+ }
+
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
@@ -168,8 +276,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
}
private RemoteCallback wrapRemoteCallback(
- @androidx.annotation.NonNull Consumer<Bundle> contentCallback) {
+ @NonNull Consumer<Bundle> contentCallback) {
return new RemoteCallback(
result -> {
if (result != null) {
@@ -604,7 +721,7 @@
@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 0000000..05c603f
--- /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
similarity index 94%
rename from services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 7dd8f2f..2626cc8 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_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.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 @@
/**
* 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 @@
* {@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 @@
* {@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 @@
* {@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 @@
};
}
- 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 @@
}
} 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
similarity index 99%
rename from services/core/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/InferenceInfoStore.java
index bef3f80..e8a1b322 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.List;
import java.util.TreeSet;
+/**
+ * @hide
+ */
public class InferenceInfoStore {
private static final String TAG = "InferenceInfoStore";
private final TreeSet<InferenceInfo> inferenceInfos;
@@ -98,4 +101,4 @@
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
similarity index 60%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerInternal.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerLocal.java
index 1450dc0..6badc53 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
similarity index 84%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index b0d69e6..607ec1c 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.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.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 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 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 @@
remoteCallback.sendResult(null);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
AndroidFuture future = new AndroidFuture();
@@ -279,7 +278,7 @@
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -317,7 +316,7 @@
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -361,7 +360,7 @@
PersistableBundle.EMPTY);
return;
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -404,7 +403,7 @@
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
mRemoteOnDeviceIntelligenceService.postAsync(
service -> {
@@ -444,7 +443,7 @@
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -488,7 +487,7 @@
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -534,7 +533,7 @@
"OnDeviceIntelligenceManagerService is unavailable",
PersistableBundle.EMPTY);
}
- ensureRemoteInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ true);
int callerUid = Binder.getCallingUid();
result = mRemoteInferenceService.postAsync(
service -> {
@@ -559,20 +558,31 @@
}
@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 @@
});
}
}
+ return true;
}
@NonNull
@@ -604,13 +615,21 @@
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 @@
};
}
- 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 void onConnected(
@NonNull IOnDeviceSandboxedInferenceService service) {
try {
- ensureRemoteIntelligenceServiceInitialized();
+ if (!ensureRemoteIntelligenceServiceInitialized(
+ /* throwServiceIfInvalid */
+ false)) {
+ return;
+ }
service.registerRemoteStorageService(
getIRemoteStorageService(), new IRemoteCallback.Stub() {
@Override
@@ -659,20 +685,29 @@
@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 @@
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 @@
}
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 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 void getReadOnlyFeatureFileDescriptorMap(
Feature feature,
RemoteCallback remoteCallback) {
- ensureRemoteIntelligenceServiceInitialized();
+ ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ true);
mRemoteOnDeviceIntelligenceService.run(
service -> service.getReadOnlyFeatureFileDescriptorMap(
feature,
@@ -829,40 +878,48 @@
};
}
- 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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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
similarity index 97%
rename from services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java
index d2c84fa..c641de8 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 @@
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
similarity index 80%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
index ac9747a..0c43a30 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.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 @@
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
similarity index 82%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index 18b1383..8c5d5a7 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.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 @@
* 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 @@
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
similarity index 98%
rename from services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
rename to packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java
index 32f0698..249bcd3 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.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 @@
* 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 50db501..5033101 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/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index cd03dd7..07b1c9e 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -22,6 +22,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue
import java.util.WeakHashMap
import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
/**
* Callback to be informed of changes in [KeyedObservable] object.
@@ -203,13 +204,71 @@
}
}
- fun hasAnyObserver(): Boolean {
+ open fun hasAnyObserver(): Boolean {
synchronized(observers) { if (observers.isNotEmpty()) return true }
synchronized(keyedObservers) { if (keyedObservers.isNotEmpty()) return true }
return false
}
}
+/** [KeyedDataObservable] that maintains a counter for the observers. */
+abstract class AbstractKeyedDataObservable<K> : KeyedDataObservable<K>() {
+ /**
+ * Counter of observers.
+ *
+ * The value is accurate only when [addObserver] and [removeObserver] are invoked in pairs.
+ */
+ private val counter = AtomicInteger()
+
+ override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) =
+ if (super.addObserver(observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) =
+ if (super.addObserver(key, observer, executor)) {
+ onObserverAdded()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverAdded() {
+ if (counter.getAndIncrement() == 0) onFirstObserverAdded()
+ }
+
+ /** Callbacks when the first observer is just added. */
+ protected abstract fun onFirstObserverAdded()
+
+ override fun removeObserver(observer: KeyedObserver<K?>) =
+ if (super.removeObserver(observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ override fun removeObserver(key: K, observer: KeyedObserver<K>) =
+ if (super.removeObserver(key, observer)) {
+ onObserverRemoved()
+ true
+ } else {
+ false
+ }
+
+ private fun onObserverRemoved() {
+ if (counter.decrementAndGet() == 0) onLastObserverRemoved()
+ }
+
+ /** Callbacks when the last observer is just removed. */
+ protected abstract fun onLastObserverRemoved()
+
+ override fun hasAnyObserver() = counter.get() > 0
+}
+
/** [KeyedObservable] with no-op implementations for all interfaces. */
open class NoOpKeyedObservable<K> : KeyedObservable<K> {
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
index 04d4bfe..d6e7a89 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt
@@ -20,21 +20,10 @@
import android.database.ContentObserver
import android.net.Uri
import android.util.Log
-import java.util.concurrent.Executor
-import java.util.concurrent.atomic.AtomicInteger
/** Base class of the Settings provider data stores. */
abstract class SettingsStore(protected val contentResolver: ContentResolver) :
- KeyedDataObservable<String>(), KeyValueStore {
-
- /**
- * Counter of observers.
- *
- * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When
- * an observer is not removed (and its weak reference is garbage collected), the content
- * observer is not unregistered but this is not a big deal.
- */
- private val counter = AtomicInteger()
+ AbstractKeyedDataObservable<String>(), KeyValueStore {
private val contentObserver =
object : ContentObserver(HandlerExecutor.main) {
@@ -48,49 +37,15 @@
}
}
- override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) =
- if (super.addObserver(observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
+ /** The URI to watch for any key change. */
+ protected abstract val uri: Uri
- override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) =
- if (super.addObserver(key, observer, executor)) {
- onObserverAdded()
- true
- } else {
- false
- }
-
- private fun onObserverAdded() {
- if (counter.getAndIncrement() != 0) return
+ override fun onFirstObserverAdded() {
Log.i(tag, "registerContentObserver")
contentResolver.registerContentObserver(uri, true, contentObserver)
}
- /** The URI to watch for any key change. */
- protected abstract val uri: Uri
-
- override fun removeObserver(observer: KeyedObserver<String?>) =
- if (super.removeObserver(observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- override fun removeObserver(key: String, observer: KeyedObserver<String>) =
- if (super.removeObserver(key, observer)) {
- onObserverRemoved()
- true
- } else {
- false
- }
-
- private fun onObserverRemoved() {
- if (counter.decrementAndGet() != 0) return
+ override fun onLastObserverRemoved() {
Log.i(tag, "unregisterContentObserver")
contentResolver.unregisterContentObserver(contentObserver)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index d89d397..5a524d9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -133,7 +133,7 @@
list.joinToString(separator = System.lineSeparator())
}
if (footer.isBlank()) return
- HorizontalDivider()
+ if (!isSpaExpressiveEnabled) HorizontalDivider()
Column(
modifier =
if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding)
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index c1f254a..1a043d5 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -187,3 +187,13 @@
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/res/drawable/ic_ambient_volume.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
new file mode 100644
index 0000000..2c1f300
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume.xml
@@ -0,0 +1,44 @@
+<?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.
+-->
+
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:maxLevel="0" android:drawable="@drawable/ic_ambient_volume_0_0" />
+ <item android:maxLevel="1" android:drawable="@drawable/ic_ambient_volume_0_1" />
+ <item android:maxLevel="2" android:drawable="@drawable/ic_ambient_volume_0_2" />
+ <item android:maxLevel="3" android:drawable="@drawable/ic_ambient_volume_0_3" />
+ <item android:maxLevel="4" android:drawable="@drawable/ic_ambient_volume_0_4" />
+ <item android:maxLevel="5" android:drawable="@drawable/ic_ambient_volume_1_0" />
+ <item android:maxLevel="6" android:drawable="@drawable/ic_ambient_volume_1_1" />
+ <item android:maxLevel="7" android:drawable="@drawable/ic_ambient_volume_1_2" />
+ <item android:maxLevel="8" android:drawable="@drawable/ic_ambient_volume_1_3" />
+ <item android:maxLevel="9" android:drawable="@drawable/ic_ambient_volume_1_4" />
+ <item android:maxLevel="10" android:drawable="@drawable/ic_ambient_volume_2_0" />
+ <item android:maxLevel="11" android:drawable="@drawable/ic_ambient_volume_2_1" />
+ <item android:maxLevel="12" android:drawable="@drawable/ic_ambient_volume_2_2" />
+ <item android:maxLevel="13" android:drawable="@drawable/ic_ambient_volume_2_3" />
+ <item android:maxLevel="14" android:drawable="@drawable/ic_ambient_volume_2_4" />
+ <item android:maxLevel="15" android:drawable="@drawable/ic_ambient_volume_3_0" />
+ <item android:maxLevel="16" android:drawable="@drawable/ic_ambient_volume_3_1" />
+ <item android:maxLevel="17" android:drawable="@drawable/ic_ambient_volume_3_2" />
+ <item android:maxLevel="18" android:drawable="@drawable/ic_ambient_volume_3_3" />
+ <item android:maxLevel="19" android:drawable="@drawable/ic_ambient_volume_3_4" />
+ <item android:maxLevel="20" android:drawable="@drawable/ic_ambient_volume_4_0" />
+ <item android:maxLevel="21" android:drawable="@drawable/ic_ambient_volume_4_1" />
+ <item android:maxLevel="22" android:drawable="@drawable/ic_ambient_volume_4_2" />
+ <item android:maxLevel="23" android:drawable="@drawable/ic_ambient_volume_4_3" />
+ <item android:maxLevel="24" android:drawable="@drawable/ic_ambient_volume_4_4" />
+</level-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
new file mode 100644
index 0000000..738fe89
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_0.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M792,904L671,783Q646,799 618,810.5Q590,822 560,829L560,747Q574,742 587.5,737Q601,732 613,725L480,592L480,800L280,600L120,600L120,360L248,360L56,168L112,112L848,848L792,904ZM784,672L726,614Q743,583 751.5,549Q760,515 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,532 825.5,581Q811,630 784,672ZM650,538L560,448L560,318Q607,340 633.5,384Q660,428 660,480Q660,495 657.5,509.5Q655,524 650,538ZM480,368L376,264L480,160L480,368ZM400,606L400,512L328,440L328,440L200,440L200,520L314,520L400,606ZM364,476L364,476L364,476L364,476L364,476L364,476L364,476Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
new file mode 100644
index 0000000..8de24f9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_1.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
new file mode 100644
index 0000000..267093e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_2.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
new file mode 100644
index 0000000..fb21254
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_3.xml
@@ -0,0 +1,35 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
new file mode 100644
index 0000000..4f1e054
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_0_4.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
new file mode 100644
index 0000000..d151fee
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_0.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
new file mode 100644
index 0000000..3e97a2e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_1.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
new file mode 100644
index 0000000..7bfd662
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_2.xml
@@ -0,0 +1,35 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
new file mode 100644
index 0000000..bba33b3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_3.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
new file mode 100644
index 0000000..c004316
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_1_4.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
new file mode 100644
index 0000000..d89ed87
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_0.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
new file mode 100644
index 0000000..e0a9c41
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_1.xml
@@ -0,0 +1,35 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
new file mode 100644
index 0000000..8627af9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_2.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
new file mode 100644
index 0000000..2c1139a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_3.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
new file mode 100644
index 0000000..8d920b1
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_2_4.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.937L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
new file mode 100644
index 0000000..7fd4e17
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_0.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.043,0.761L8.814,0.286L9.887,4.192L8.116,4.666L7.043,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.02,3.652L4.317,2.355L7.162,5.201L5.866,6.497L3.02,3.652Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.898,8.942L1.153,7.127L5.163,7.636L4.908,9.451L0.898,8.942Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.382L4.666,7.566L5.162,7.627L4.907,9.46L4.411,9.382Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.589,13.874L1.053,12.121L4.93,10.941L5.466,12.694L1.589,13.874Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
new file mode 100644
index 0000000..f0f8150
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_1.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
new file mode 100644
index 0000000..1bb2017
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_2.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
new file mode 100644
index 0000000..a8bc0af
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_3.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.163,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
new file mode 100644
index 0000000..f8a5832
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_3_4.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.652L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.402,2.08L9.173,1.606L9.887,4.187L8.116,4.661L7.402,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.765,3.415L9.536,2.94L9.887,4.194L8.116,4.669L7.765,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M7.98,4.19L9.751,3.716L9.887,4.19L8.116,4.665L7.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.985,4.621L5.281,3.324L7.162,5.21L5.866,6.507L3.985,4.621Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M4.941,5.574L6.238,4.278L7.162,5.202L5.866,6.498L4.941,5.574Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M5.523,6.152L6.82,4.856L7.165,5.198L5.869,6.494L5.523,6.152Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.251,9.109L2.506,7.293L5.163,7.626L4.908,9.442L2.251,9.109Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M3.658,9.284L3.913,7.469L5.163,7.625L4.908,9.44L3.658,9.284Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.411,9.381L4.666,7.566L5.162,7.627L4.907,9.459L4.411,9.381Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.933,13.463L2.397,11.71L4.93,10.948L5.466,12.702L2.933,13.463Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.254,13.058L3.718,11.304L4.923,10.942L5.459,12.695L4.254,13.058Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M4.919,12.853L4.383,11.1L4.919,10.936L5.455,12.69L4.919,12.853Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
new file mode 100644
index 0000000..e5d7ad4
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_0.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
new file mode 100644
index 0000000..f5cdf5d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_1.xml
@@ -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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
new file mode 100644
index 0000000..cbed634
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_2.xml
@@ -0,0 +1,35 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
new file mode 100644
index 0000000..90d81d8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_3.xml
@@ -0,0 +1,39 @@
+<!--
+ 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="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.974,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.974,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.879,8.868L1.134,7.052L5.163,7.632L4.908,9.447L0.879,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.566,12.658L18.099,13.419Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
new file mode 100644
index 0000000..f1a9a8a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_ambient_volume_4_4.xml
@@ -0,0 +1,43 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="22"
+ android:viewportHeight="22">
+ <path android:fillColor="#ffffff" android:pathData="M17.987,18.375V20.987H3.013V18.375C3.013,17.862 3.145,17.379 3.409,16.928C3.674,16.477 4.039,16.135 4.506,15.902L4.506,15.902C5.302,15.496 6.199,15.153 7.199,14.872C8.197,14.591 9.298,14.45 10.5,14.45C11.702,14.45 12.802,14.591 13.801,14.872C14.8,15.153 15.698,15.496 16.494,15.902L16.494,15.902C16.961,16.135 17.326,16.477 17.59,16.928C17.855,17.379 17.987,17.862 17.987,18.375ZM10.5,13.487C9.472,13.487 8.593,13.121 7.861,12.389C7.129,11.657 6.763,10.778 6.763,9.75C6.763,8.722 7.129,7.843 7.861,7.111C8.593,6.379 9.472,6.013 10.5,6.013C11.528,6.013 12.407,6.379 13.139,7.111C13.871,7.843 14.237,8.722 14.237,9.75C14.237,10.778 13.871,11.657 13.139,12.389C12.407,13.121 11.528,13.487 10.5,13.487Z" android:strokeColor="#ffffff" android:strokeWidth="0.0255682"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M6.983,0.765L8.754,0.29L9.947,4.192L8.177,4.666L6.983,0.765Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M2.975,3.583L4.271,2.287L7.16,5.211L5.864,6.507L2.975,3.583Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M0.878,8.868L1.134,7.052L5.163,7.632L4.907,9.447L0.878,8.868Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M1.601,14.021L1.065,12.268L4.918,10.898L5.454,12.651L1.601,14.021Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.917,0.761L12.146,0.286L11.073,4.192L12.844,4.666L13.917,0.761Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M18.002,3.661L16.706,2.365L13.86,5.211L15.157,6.507L18.002,3.661Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M20.123,8.938L19.868,7.123L15.858,7.632L16.113,9.447L20.123,8.938Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M19.443,13.831L19.979,12.078L16.103,10.898L15.567,12.651L19.443,13.831Z"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M13.558,2.08L11.787,1.606L11.073,4.187L12.844,4.661L13.558,2.08Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M17.038,4.63L15.742,3.334L13.86,5.22L15.157,6.517L17.038,4.63Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.77,9.105L18.515,7.289L15.858,7.622L16.113,9.438L18.77,9.105Z" android:strokeAlpha="0.4"/>
+ <path android:fillAlpha="0.4" android:fillColor="#D9D9D9" android:pathData="M18.099,13.419L18.635,11.666L16.103,10.905L15.567,12.658L18.099,13.419Z" android:strokeAlpha="0.4"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M13.195,3.415L11.424,2.94L11.073,4.194L12.844,4.669L13.195,3.415Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.081,5.584L14.785,4.288L13.86,5.212L15.157,6.508L16.081,5.584Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M17.363,9.28L17.108,7.465L15.858,7.621L16.113,9.436L17.363,9.28Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.778,13.014L17.314,11.261L16.109,10.898L15.573,12.651L16.778,13.014Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M12.98,4.19L11.209,3.716L11.073,4.19L12.844,4.665L12.98,4.19Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M15.5,6.162L14.203,4.866L13.858,5.208L15.154,6.504L15.5,6.162Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.61,9.378L16.355,7.562L15.859,7.623L16.114,9.456L16.61,9.378Z"/>
+ <path android:fillColor="#D9D9D9" android:pathData="M16.113,12.81L16.649,11.057L16.113,10.893L15.577,12.646L16.113,12.81Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 216574a..429e4c9 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 @@
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/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 35e3dd3..e1be1d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
public SignalStrength signalStrength;
public TelephonyDisplayInfo telephonyDisplayInfo =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
/**
* Empty constructor
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1f291cd..731cb72 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 @@
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 abd5b9a..039832c 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 @@
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 7eff16b..0367fe0 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 9ab853f..326bff4 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.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.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 @@
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 @@
// 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 @@
.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
return baos.toByteArray();
}
- private void restoreSettings(
+ @VisibleForTesting
+ void restoreSettings(
BackupDataInput data,
Uri contentUri,
Set<String> movedToGlobal,
@@ -781,12 +827,17 @@
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 @@
movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
- settingsToPreserve);
+ settingsToPreserve,
+ settingsKey);
}
private void restoreSettings(
@@ -810,7 +862,8 @@
Set<String> movedToSystem,
int blockedSettingsArrayId,
Set<String> dynamicBlockList,
- Set<String> settingsToPreserve) {
+ Set<String> settingsToPreserve,
+ String settingsKey) {
restoreSettings(
settings,
0,
@@ -821,7 +874,8 @@
movedToSystem,
blockedSettingsArrayId,
dynamicBlockList,
- settingsToPreserve);
+ settingsToPreserve,
+ settingsKey);
}
@VisibleForTesting
@@ -835,12 +889,13 @@
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 @@
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 @@
+ " 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 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
@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 @@
*
* @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 @@
}
}
+ if (areAgentMetricsEnabled) {
+ numberOfSettingsPerKey.put(settingsKey, backedUpSettingIndex);
+ }
+
// Aggregate the result.
byte[] result = new byte[totalSize];
int pos = 0;
@@ -1364,7 +1470,9 @@
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 @@
null,
blockedSettingsArrayId,
dynamicBlocklist,
- preservedSettings);
+ preservedSettings,
+ KEY_DEVICE_SPECIFIC_CONFIG);
updateWindowManagerIfNeeded(originalDensity);
@@ -1597,14 +1706,14 @@
* 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 c0e61ee..7aed615 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 @@
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 @@
}
}
- // 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 aca26ec..cfd27c6 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 @@
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 3a391505..350c149 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.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 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.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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
mBlockedSettings = blockedSettings;
}
- void setSettingsWhitelist(SettingsBackupWhitelist settingsWhitelist) {
- mSettingsWhitelist = settingsWhitelist;
+ void setSettingsAllowlist(SettingsBackupAllowlist settingsAllowlist) {
+ mSettingsAllowlist = settingsAllowlist;
}
@Override
@@ -369,12 +919,18 @@
}
@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 48ce49d..276b206 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.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 @@
}
@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/Android.bp b/packages/Shell/Android.bp
index 5f81085..5fdf045 100644
--- a/packages/Shell/Android.bp
+++ b/packages/Shell/Android.bp
@@ -12,7 +12,10 @@
"src/**/*.java",
":dumpstate_aidl",
]
-shell_static_libs = ["androidx.legacy_legacy-support-v4"]
+shell_static_libs = [
+ "androidx.legacy_legacy-support-v4",
+ "wear_aconfig_declarations_flags_java_lib",
+]
android_app {
name: "Shell",
@@ -28,6 +31,7 @@
flags_packages: [
"android.security.flags-aconfig",
"android.permission.flags-aconfig",
+ "wear_aconfig_declarations",
],
platform_apis: true,
certificate: "platform",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fa6e2db..baf829a 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -740,6 +740,9 @@
<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 0000000..b693cc8
--- /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 7f25b51..0694b61 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -24,6 +24,7 @@
import static com.android.shell.BugreportPrefs.STATE_HIDE;
import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.flags.Flags.handleBugreportsForWear;
import android.accounts.Account;
import android.accounts.AccountManager;
@@ -89,10 +90,10 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.google.android.collect.Lists;
-
import libcore.io.Streams;
+import com.google.android.collect.Lists;
+
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -140,6 +141,7 @@
public class BugreportProgressService extends Service {
private static final String TAG = "BugreportProgressService";
private static final boolean DEBUG = false;
+ private static final String WRITE_AND_APPEND_MODE = "wa";
private Intent startSelfIntent;
@@ -384,7 +386,11 @@
}
private static String getFileName(BugreportInfo info, String suffix) {
- return String.format("%s-%s%s", info.baseName, info.getName(), suffix);
+ return getFileName(suffix, info.baseName, info.getName());
+ }
+
+ private static String getFileName(String suffix, String baseName, String name) {
+ return String.format("%s-%s%s", baseName, name, suffix);
}
private final class BugreportCallbackImpl extends BugreportCallback {
@@ -420,14 +426,14 @@
@Override
public void onFinished() {
- mInfo.renameBugreportFile();
- mInfo.renameScreenshots();
- if (mInfo.bugreportFile.length() == 0) {
- Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportFile);
- onError(BUGREPORT_ERROR_RUNTIME);
- return;
- }
synchronized (mLock) {
+ mInfo.renameBugreportFile();
+ mInfo.renameScreenshots();
+ if (mInfo.bugreportLocationInfo.isFileEmpty(mContext)) {
+ Log.e(TAG, "Bugreport file empty. File path = " + mInfo.bugreportLocationInfo);
+ onError(BUGREPORT_ERROR_RUNTIME);
+ return;
+ }
sendBugreportFinishedBroadcastLocked();
mMainThreadHandler.post(() -> mInfoDialog.onBugreportFinished(mInfo));
}
@@ -454,15 +460,15 @@
@GuardedBy("mLock")
private void sendBugreportFinishedBroadcastLocked() {
- final String bugreportFilePath = mInfo.bugreportFile.getAbsolutePath();
- if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE) {
- sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
- mInfo.bugreportFile, mInfo.nonce);
+ File bugreportFile = mInfo.bugreportLocationInfo.mBugreportFile;
+ if (mInfo.type == BugreportParams.BUGREPORT_MODE_REMOTE && bugreportFile != null) {
+ sendRemoteBugreportFinishedBroadcast(
+ mContext, bugreportFile.getAbsolutePath(), bugreportFile, mInfo.nonce);
} else {
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
- intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
- intent.putExtra(EXTRA_SCREENSHOT, getScreenshotForIntent(mInfo));
+ intent.putExtra(EXTRA_BUGREPORT, mInfo.bugreportLocationInfo.getBugreportPath());
+ intent.putExtra(EXTRA_SCREENSHOT, mInfo.screenshotLocationInfo.getScreenshotPath());
mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
onBugreportFinished(mInfo);
}
@@ -498,19 +504,6 @@
android.Manifest.permission.DUMP);
}
- /**
- * Checks if screenshot array is non-empty and returns the first screenshot's path. The first
- * screenshot is the default screenshot for the bugreport types that take it.
- */
- private static String getScreenshotForIntent(BugreportInfo info) {
- if (!info.screenshotFiles.isEmpty()) {
- final File screenshotFile = info.screenshotFiles.get(0);
- final String screenshotFilePath = screenshotFile.getAbsolutePath();
- return screenshotFilePath;
- }
- return null;
- }
-
private static String generateFileHash(String fileName) {
String fileHash = null;
try {
@@ -715,24 +708,28 @@
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
List<Uri> extraAttachments =
intent.getParcelableArrayListExtra(EXTRA_EXTRA_ATTACHMENT_URIS, Uri.class);
-
- BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
- shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachments);
- synchronized (mLock) {
- if (info.bugreportFile.exists()) {
- Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
- + info.bugreportFile + " already exists");
- return;
- }
- info.createBugreportFile();
+ BugreportInfo info =
+ setupFilesAndCreateBugreportInfo(
+ intent,
+ bugreportType,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ nonce,
+ extraAttachments);
+ if (info == null) {
+ Log.e(TAG, "Could not initialize bugreport inputs");
+ return;
}
+
ParcelFileDescriptor bugreportFd = info.getBugreportFd();
if (bugreportFd == null) {
Log.e(TAG, "Failed to start bugreport generation as "
+ " bugreport parcel file descriptor is null.");
return;
}
- info.createScreenshotFile(mBugreportsDir);
+
ParcelFileDescriptor screenshotFd = null;
if (isDefaultScreenshotRequired(bugreportType, /* hasScreenshotButton= */ !mIsTv)) {
screenshotFd = info.getDefaultScreenshotFd();
@@ -740,7 +737,7 @@
Log.e(TAG, "Failed to start bugreport generation as"
+ " screenshot parcel file descriptor is null. Deleting bugreport file");
FileUtils.closeQuietly(bugreportFd);
- info.bugreportFile.delete();
+ info.bugreportLocationInfo.maybeDeleteBugreportFile();
return;
}
}
@@ -768,6 +765,56 @@
}
}
+ // Sets up BugreportInfo. If needed, creates bugreport and screenshot files.
+ private BugreportInfo setupFilesAndCreateBugreportInfo(
+ Intent intent,
+ int bugreportType,
+ String baseName,
+ String name,
+ String shareTitle,
+ String shareDescription,
+ long nonce,
+ List<Uri> extraAttachments) {
+ ArrayList<Uri> brAndScreenshot;
+ Uri bugReportUri = null;
+ Uri screenshotUri = null;
+
+ if (handleBugreportsForWear() && bugreportType == BugreportParams.BUGREPORT_MODE_WEAR) {
+ brAndScreenshot = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ if (brAndScreenshot != null && !brAndScreenshot.isEmpty()) {
+ bugReportUri = brAndScreenshot.get(0);
+ if (bugReportUri == null) {
+ Log.e(TAG, "Can't start bugreport request. Bugreport uri is null.");
+ return null;
+ }
+ screenshotUri = (brAndScreenshot.size() > 1) ? brAndScreenshot.get(1) : null;
+ }
+ }
+
+ BugreportLocationInfo bugreportLocationInfo =
+ new BugreportLocationInfo(bugReportUri, mBugreportsDir, baseName, name);
+ ScreenshotLocationInfo screenshotLocationInfo = new ScreenshotLocationInfo(screenshotUri);
+ BugreportInfo info =
+ new BugreportInfo(
+ mContext,
+ baseName,
+ name,
+ shareTitle,
+ shareDescription,
+ bugreportType,
+ nonce,
+ extraAttachments,
+ bugreportLocationInfo,
+ screenshotLocationInfo);
+ synchronized (mLock) {
+ if (!bugreportLocationInfo.maybeCreateBugreportFile()) {
+ return null;
+ }
+ }
+ info.maybeCreateScreenshotFile(mBugreportsDir);
+ return info;
+ }
+
private static boolean isDefaultScreenshotRequired(
@BugreportParams.BugreportMode int bugreportType,
boolean hasScreenshotButton) {
@@ -1177,8 +1224,9 @@
stopForegroundWhenDoneLocked(info.id);
}
- if (!info.bugreportFile.exists() || !info.bugreportFile.canRead()) {
- Log.e(TAG, "Could not read bugreport file " + info.bugreportFile);
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ if (!info.bugreportLocationInfo.isValidBugreportResult()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
Toast.makeText(mContext, R.string.bugreport_unreadable_text, Toast.LENGTH_LONG).show();
synchronized (mLock) {
stopProgressLocked(info.id);
@@ -1194,7 +1242,7 @@
* the bugreport.
*/
private void triggerLocalNotification(final BugreportInfo info) {
- boolean isPlainText = info.bugreportFile.getName().toLowerCase().endsWith(".txt");
+ boolean isPlainText = info.bugreportLocationInfo.isPlainText();
if (!isPlainText) {
// Already zipped, send it right away.
sendBugreportNotification(info, mTakingScreenshot);
@@ -1223,11 +1271,11 @@
// grant temporary permissions for.
final Uri bugreportUri;
try {
- bugreportUri = getUri(context, info.bugreportFile);
+ bugreportUri = getUri(context, info.bugreportLocationInfo.mBugreportFile);
} catch (IllegalArgumentException e) {
// Should not happen on production, but happens when a Shell is sideloaded and
// FileProvider cannot find a configured root for it.
- Log.wtf(TAG, "Could not get URI for " + info.bugreportFile, e);
+ Log.wtf(TAG, "Could not get URI for " + info.bugreportLocationInfo.mBugreportFile, e);
return null;
}
@@ -1258,7 +1306,7 @@
new ClipData.Item(null, null, null, bugreportUri));
Log.d(TAG, "share intent: bureportUri=" + bugreportUri);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
- for (File screenshot : info.screenshotFiles) {
+ for (File screenshot : info.screenshotLocationInfo.mScreenshotFiles) {
final Uri screenshotUri = getUri(context, screenshot);
Log.d(TAG, "share intent: screenshotUri=" + screenshotUri);
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
@@ -1317,7 +1365,11 @@
*/
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);
@@ -1512,22 +1564,25 @@
* original in case of failure).
*/
private static void zipBugreport(BugreportInfo info) {
- final String bugreportPath = info.bugreportFile.getAbsolutePath();
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final String bugreportPath = bugreportFile.getAbsolutePath();
final String zippedPath = bugreportPath.replace(".txt", ".zip");
Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
final File bugreportZippedFile = new File(zippedPath);
- try (InputStream is = new FileInputStream(info.bugreportFile);
- ZipOutputStream zos = new ZipOutputStream(
- new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
- addEntry(zos, info.bugreportFile.getName(), is);
+ try (InputStream is = new FileInputStream(bugreportFile);
+ ZipOutputStream zos =
+ new ZipOutputStream(
+ new BufferedOutputStream(
+ new FileOutputStream(bugreportZippedFile)))) {
+ addEntry(zos, bugreportFile.getName(), is);
// Delete old file
- final boolean deleted = info.bugreportFile.delete();
+ final boolean deleted = bugreportFile.delete();
if (deleted) {
Log.v(TAG, "deleted original bugreport (" + bugreportPath + ")");
} else {
Log.e(TAG, "could not delete original bugreport (" + bugreportPath + ")");
}
- info.bugreportFile = bugreportZippedFile;
+ info.bugreportLocationInfo.mBugreportFile = bugreportZippedFile;
} catch (IOException e) {
Log.e(TAG, "exception zipping file " + zippedPath, e);
}
@@ -1557,7 +1612,11 @@
@GuardedBy("mLock")
private void addDetailsToZipFileLocked(BugreportInfo info) {
- if (info.bugreportFile == null) {
+ if (handleBugreportsForWear()) {
+ Log.d(TAG, "Skipping adding details to zipped file");
+ return;
+ }
+ if (info.bugreportLocationInfo.mBugreportFile == null) {
// One possible reason is a bug in the Parcelization code.
Log.wtf(TAG, "addDetailsToZipFile(): no bugreportFile on " + info);
return;
@@ -1588,10 +1647,11 @@
sendBugreportBeingUpdatedNotification(mContext, info.id); // ...and that takes time
}
- final File dir = info.bugreportFile.getParentFile();
- final File tmpZip = new File(dir, "tmp-" + info.bugreportFile.getName());
+ File bugreportFile = info.bugreportLocationInfo.mBugreportFile;
+ final File dir = bugreportFile.getParentFile();
+ final File tmpZip = new File(dir, "tmp-" + bugreportFile.getName());
Log.d(TAG, "Writing temporary zip file (" + tmpZip + ") with title and/or description");
- try (ZipFile oldZip = new ZipFile(info.bugreportFile);
+ try (ZipFile oldZip = new ZipFile(bugreportFile);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tmpZip))) {
// First copy contents from original zip.
@@ -1628,8 +1688,8 @@
stopForegroundWhenDoneLocked(info.id);
}
- if (!tmpZip.renameTo(info.bugreportFile)) {
- Log.e(TAG, "Could not rename " + tmpZip + " to " + info.bugreportFile);
+ if (!tmpZip.renameTo(bugreportFile)) {
+ Log.e(TAG, "Could not rename " + tmpZip + " to " + bugreportFile);
}
}
@@ -2087,15 +2147,9 @@
*/
String formattedLastUpdate;
- /**
- * Path of the main bugreport file.
- */
- File bugreportFile;
+ BugreportLocationInfo bugreportLocationInfo;
- /**
- * Path of the screenshot files.
- */
- List<File> screenshotFiles = new ArrayList<>(1);
+ ScreenshotLocationInfo screenshotLocationInfo;
/**
* Whether dumpstate sent an intent informing it has finished.
@@ -2138,10 +2192,17 @@
/**
* Constructor for tracked bugreports - typically called upon receiving BUGREPORT_REQUESTED.
*/
- BugreportInfo(Context context, String baseName, String name,
- @Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
- @Nullable List<Uri> extraAttachments) {
+ BugreportInfo(
+ Context context,
+ String baseName,
+ String name,
+ @Nullable String shareTitle,
+ @Nullable String shareDescription,
+ @BugreportParams.BugreportMode int type,
+ long nonce,
+ @Nullable List<Uri> extraAttachments,
+ BugreportLocationInfo bugreportLocationInfo,
+ ScreenshotLocationInfo screenshotLocationInfo) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2149,29 +2210,27 @@
this.type = type;
this.nonce = nonce;
this.baseName = baseName;
- this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.bugreportLocationInfo = bugreportLocationInfo;
+ this.screenshotLocationInfo = screenshotLocationInfo;
this.extraAttachments = extraAttachments;
}
- void createBugreportFile() {
- createReadWriteFile(bugreportFile);
- }
-
- void createScreenshotFile(File bugreportsDir) {
+ void maybeCreateScreenshotFile(File bugreportsDir) {
+ if (screenshotLocationInfo.mScreenshotUri != null) {
+ // Screenshot file was already created.
+ return;
+ }
File screenshotFile = new File(bugreportsDir, getScreenshotName("default"));
addScreenshot(screenshotFile);
createReadWriteFile(screenshotFile);
}
ParcelFileDescriptor getBugreportFd() {
- return getFd(bugreportFile);
+ return bugreportLocationInfo.getBugreportFd(context);
}
ParcelFileDescriptor getDefaultScreenshotFd() {
- if (screenshotFiles.isEmpty()) {
- return null;
- }
- return getFd(screenshotFiles.get(0));
+ return screenshotLocationInfo.getScreenshotFd(context);
}
void setTitle(String title) {
@@ -2229,14 +2288,14 @@
* Saves the location of a taken screenshot so it can be sent out at the end.
*/
void addScreenshot(File screenshot) {
- screenshotFiles.add(screenshot);
+ screenshotLocationInfo.mScreenshotFiles.add(screenshot);
}
/**
* Deletes all screenshots taken for a given bugreport.
*/
private void deleteScreenshots() {
- for (File file : screenshotFiles) {
+ for (File file : screenshotLocationInfo.mScreenshotFiles) {
Log.i(TAG, "Deleting screenshot file " + file);
file.delete();
}
@@ -2246,18 +2305,14 @@
* Deletes bugreport file for a given bugreport.
*/
private void deleteBugreportFile() {
- Log.i(TAG, "Deleting bugreport file " + bugreportFile);
- bugreportFile.delete();
+ bugreportLocationInfo.maybeDeleteBugreportFile();
}
/**
* Deletes empty files for a given bugreport.
*/
private void deleteEmptyFiles() {
- if (bugreportFile.length() == 0) {
- Log.i(TAG, "Deleting empty bugreport file: " + bugreportFile);
- bugreportFile.delete();
- }
+ bugreportLocationInfo.maybeDeleteEmptyBugreport();
deleteEmptyScreenshots();
}
@@ -2265,14 +2320,7 @@
* Deletes empty screenshot files.
*/
private void deleteEmptyScreenshots() {
- screenshotFiles.removeIf(file -> {
- final long length = file.length();
- if (length == 0) {
- Log.i(TAG, "Deleting empty screenshot file: " + file);
- file.delete();
- }
- return length == 0;
- });
+ screenshotLocationInfo.deleteEmptyScreenshots();
}
/**
@@ -2280,43 +2328,14 @@
* {@code initialName} if user has changed it.
*/
void renameScreenshots() {
- deleteEmptyScreenshots();
- if (TextUtils.isEmpty(name) || screenshotFiles.isEmpty()) {
- return;
- }
- final List<File> renamedFiles = new ArrayList<>(screenshotFiles.size());
- for (File oldFile : screenshotFiles) {
- final String oldName = oldFile.getName();
- final String newName = oldName.replaceFirst(initialName, name);
- final File newFile;
- if (!newName.equals(oldName)) {
- final File renamedFile = new File(oldFile.getParentFile(), newName);
- Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
- newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
- } else {
- Log.w(TAG, "Name didn't change: " + oldName);
- newFile = oldFile;
- }
- if (newFile.length() > 0) {
- renamedFiles.add(newFile);
- } else if (newFile.delete()) {
- Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
- }
- }
- screenshotFiles = renamedFiles;
+ screenshotLocationInfo.renameScreenshots(initialName, name);
}
/**
* Rename bugreport file to include the name given by user via UI
*/
void renameBugreportFile() {
- File newBugreportFile = new File(bugreportFile.getParentFile(),
- getFileName(this, ".zip"));
- if (!newBugreportFile.getPath().equals(bugreportFile.getPath())) {
- if (bugreportFile.renameTo(newBugreportFile)) {
- bugreportFile = newBugreportFile;
- }
- }
+ bugreportLocationInfo.maybeRenameBugreportFile(this);
}
String getFormattedLastUpdate() {
@@ -2349,16 +2368,23 @@
builder.append("(").append(description.length()).append(" chars)");
}
- return builder
- .append("\n\tfile: ").append(bugreportFile)
- .append("\n\tscreenshots: ").append(screenshotFiles)
- .append("\n\tprogress: ").append(progress)
- .append("\n\tlast_update: ").append(getFormattedLastUpdate())
- .append("\n\taddingDetailsToZip: ").append(addingDetailsToZip)
- .append(" addedDetailsToZip: ").append(addedDetailsToZip)
- .append("\n\tshareDescription: ").append(shareDescription)
- .append("\n\tshareTitle: ").append(shareTitle)
- .toString();
+ return builder.append("\n\tfile: ")
+ .append(bugreportLocationInfo)
+ .append("\n\tscreenshots: ")
+ .append(screenshotLocationInfo)
+ .append("\n\tprogress: ")
+ .append(progress)
+ .append("\n\tlast_update: ")
+ .append(getFormattedLastUpdate())
+ .append("\n\taddingDetailsToZip: ")
+ .append(addingDetailsToZip)
+ .append(" addedDetailsToZip: ")
+ .append(addedDetailsToZip)
+ .append("\n\tshareDescription: ")
+ .append(shareDescription)
+ .append("\n\tshareTitle: ")
+ .append(shareTitle)
+ .toString();
}
// Parcelable contract
@@ -2375,11 +2401,12 @@
lastProgress.set(in.readInt());
lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
- bugreportFile = readFile(in);
+ bugreportLocationInfo = new BugreportLocationInfo(readFile(in));
int screenshotSize = in.readInt();
for (int i = 1; i <= screenshotSize; i++) {
- screenshotFiles.add(readFile(in));
+ screenshotLocationInfo = new ScreenshotLocationInfo(null);
+ screenshotLocationInfo.mScreenshotFiles.add(readFile(in));
}
finished.set(in.readInt() == 1);
@@ -2404,10 +2431,10 @@
dest.writeInt(lastProgress.intValue());
dest.writeLong(lastUpdate.longValue());
dest.writeString(getFormattedLastUpdate());
- writeFile(dest, bugreportFile);
+ writeFile(dest, bugreportLocationInfo.mBugreportFile);
- dest.writeInt(screenshotFiles.size());
- for (File screenshotFile : screenshotFiles) {
+ dest.writeInt(screenshotLocationInfo.mScreenshotFiles.size());
+ for (File screenshotFile : screenshotLocationInfo.mScreenshotFiles) {
writeFile(dest, screenshotFile);
}
@@ -2449,6 +2476,261 @@
};
}
+ /**
+ * Class for abstracting bugreport location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create a bugreport file and
+ * manage its lifecycle.
+ */
+ private static final class BugreportLocationInfo {
+ /** Path of the main bugreport file. */
+ @Nullable private File mBugreportFile;
+
+ /** Uri to bugreport location. */
+ @Nullable private Uri mBugreportUri;
+
+ BugreportLocationInfo(File bugreportFile) {
+ this.mBugreportFile = bugreportFile;
+ }
+
+ BugreportLocationInfo(Uri bugreportUri, File bugreportsDir, String baseName, String name) {
+ if (bugreportUri != null) {
+ this.mBugreportUri = bugreportUri;
+ } else {
+ this.mBugreportFile = new File(bugreportsDir, getFileName(".zip", baseName, name));
+ }
+ }
+
+ private boolean maybeCreateBugreportFile() {
+ if (mBugreportFile != null && mBugreportFile.exists()) {
+ Log.e(
+ TAG,
+ "Failed to start bugreport generation, the requested bugreport file "
+ + mBugreportFile
+ + " already exists");
+ return false;
+ }
+ createBugreportFile();
+ return true;
+ }
+
+ private void createBugreportFile() {
+ if (mBugreportUri == null) {
+ createReadWriteFile(mBugreportFile);
+ }
+ }
+
+ private ParcelFileDescriptor getBugreportFd(Context context) {
+ if (mBugreportUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mBugreportUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting BR file descriptor", e);
+ return null;
+ }
+ }
+ if (mBugreportFile == null) {
+ Log.e(TAG, "Could not get bugreport file descriptor; bugreport file was null");
+ return null;
+ }
+ return getFd(mBugreportFile);
+ }
+
+ private void maybeDeleteBugreportFile() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ Log.i(TAG, "Deleting bugreport file " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+
+ private boolean isValidBugreportResult() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.exists() && mBugreportFile.canRead();
+ }
+ // If a bugreport uri was provided, we can't assert on whether the file exists and can
+ // be read. Assume the result is valid.
+ return true;
+ }
+
+ private void maybeDeleteEmptyBugreport() {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's
+ // lifecycle.
+ return;
+ }
+ if (mBugreportFile.length() == 0) {
+ Log.i(TAG, "Deleting empty bugreport file: " + mBugreportFile);
+ mBugreportFile.delete();
+ }
+ }
+
+ private void maybeRenameBugreportFile(BugreportInfo bugreportInfo) {
+ if (mBugreportFile == null) {
+ // This means a URI is provided and shell is not responsible for the file's naming.
+ return;
+ }
+ File newBugreportFile =
+ new File(mBugreportFile.getParentFile(), getFileName(bugreportInfo, ".zip"));
+ if (!newBugreportFile.getPath().equals(mBugreportFile.getPath())) {
+ if (mBugreportFile.renameTo(newBugreportFile)) {
+ mBugreportFile = newBugreportFile;
+ }
+ }
+ }
+
+ private boolean isPlainText() {
+ if (mBugreportFile != null) {
+ return mBugreportFile.getName().toLowerCase().endsWith(".txt");
+ }
+ return false;
+ }
+
+ private boolean isFileEmpty(Context context) {
+ if (mBugreportFile != null) {
+ return mBugreportFile.length() == 0;
+ }
+ return getBugreportFd(context).getStatSize() == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "BugreportLocationInfo{"
+ + "bugreportFile="
+ + mBugreportFile
+ + ", bugreportUri="
+ + mBugreportUri
+ + '}';
+ }
+
+ private String getBugreportPath() {
+ if (mBugreportUri != null) {
+ return mBugreportUri.getLastPathSegment();
+ }
+ return mBugreportFile.getAbsolutePath();
+ }
+ }
+
+ /**
+ * Class for abstracting screenshot location. There are two possible cases:
+ * <li>If a bugreport request included a URI for bugreports of type {@link
+ * BugreportParams.BUGREPORT_MODE_WEAR}, then the URI file descriptor will be used. The
+ * requesting app manages the creation and lifecycle of the file.
+ * <li>If no URI is provided in the bugreport request, Shell will create the screenshot file and
+ * manage its lifecycle.
+ */
+ private static final class ScreenshotLocationInfo {
+
+ /** Uri to screenshot location. */
+ @Nullable private Uri mScreenshotUri;
+
+ /** Path to screenshot files. */
+ private List<File> mScreenshotFiles = new ArrayList<>(1);
+
+ ScreenshotLocationInfo(Uri screenshotUri) {
+ if (screenshotUri != null) {
+ this.mScreenshotUri = screenshotUri;
+ }
+ }
+
+ private ParcelFileDescriptor getScreenshotFd(Context context) {
+ if (mScreenshotUri != null) {
+ try {
+ return context.getContentResolver()
+ .openFileDescriptor(mScreenshotUri, WRITE_AND_APPEND_MODE);
+ } catch (Exception e) {
+ Log.d(TAG, "Faced exception when getting screenshot file", e);
+ return null;
+ }
+ }
+
+ if (mScreenshotFiles.isEmpty()) {
+ return null;
+ }
+ return getFd(mScreenshotFiles.getFirst());
+ }
+
+ @Override
+ public String toString() {
+ return "ScreenshotLocationInfo{"
+ + "screenshotUri="
+ + mScreenshotUri
+ + ", screenshotFiles="
+ + mScreenshotFiles
+ + '}';
+ }
+
+ private String getScreenshotPath() {
+ if (mScreenshotUri != null) {
+ return mScreenshotUri.getLastPathSegment();
+ }
+ return getScreenshotForIntent();
+ }
+
+ private void renameScreenshots(String initialName, String name) {
+ if (mScreenshotUri != null) {
+ // If a screenshot uri is provided, then shell is not responsible for the
+ // screenshot's naming.
+ return;
+ }
+ deleteEmptyScreenshots();
+ if (TextUtils.isEmpty(name) || mScreenshotFiles.isEmpty()) {
+ // If there is no user set name for screenshot file or there are no screenshot
+ // files, there's nothing to do.
+ return;
+ }
+ final List<File> renamedFiles = new ArrayList<>(mScreenshotFiles.size());
+ for (File oldFile : mScreenshotFiles) {
+ final String oldName = oldFile.getName();
+ final String newName = oldName.replaceFirst(initialName, name);
+ final File newFile;
+ if (!newName.equals(oldName)) {
+ final File renamedFile = new File(oldFile.getParentFile(), newName);
+ Log.d(TAG, "Renaming screenshot file " + oldFile + " to " + renamedFile);
+ newFile = oldFile.renameTo(renamedFile) ? renamedFile : oldFile;
+ } else {
+ Log.w(TAG, "Name didn't change: " + oldName);
+ newFile = oldFile;
+ }
+ if (newFile.length() > 0) {
+ renamedFiles.add(newFile);
+ } else if (newFile.delete()) {
+ Log.d(TAG, "screenshot file: " + newFile + " deleted successfully.");
+ }
+ }
+ mScreenshotFiles = renamedFiles;
+ }
+
+ private void deleteEmptyScreenshots() {
+ mScreenshotFiles.removeIf(
+ file -> {
+ final long length = file.length();
+ if (length == 0) {
+ Log.i(TAG, "Deleting empty screenshot file: " + file);
+ file.delete();
+ }
+ return length == 0;
+ });
+ }
+
+ /**
+ * Checks if screenshot array is non-empty and returns the first screenshot's path. The
+ * first screenshot is the default screenshot for the bugreport types that take it.
+ */
+ private String getScreenshotForIntent() {
+ if (!mScreenshotFiles.isEmpty()) {
+ final File screenshotFile = mScreenshotFiles.getFirst();
+ return screenshotFile.getAbsolutePath();
+ }
+ return null;
+ }
+ }
+
@GuardedBy("mLock")
private void checkProgressUpdatedLocked(BugreportInfo info, int progress) {
if (progress > CAPPED_PROGRESS) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5519b51..11cb070 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 c1f7868..ee22915 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1230,13 +1230,6 @@
}
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 3eeaf41..41a00f5 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 @@
) {
// 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 @@
// If a [controller.windowAnimatorState] exists, treat this like a takeover.
takeOverAnimationInternal(
window,
- startWindowStates = null,
+ startWindowState = null,
startTransaction = null,
callback,
)
@@ -1168,22 +1171,23 @@
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 @@
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 @@
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 @@
}
}
- // 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 @@
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 @@
fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
drawHole = !controller.isBelowAnimatingWindow,
startVelocity = velocityPxPerS,
+ startFrameTime = windowState?.timestamp ?: -1,
)
}
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 e2bc409..4e889e9 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.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 @@
@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 @@
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 @@
* 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 @@
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 @@
fadeWindowBackgroundLayer,
drawHole,
startVelocity,
+ startFrameTime,
)
.apply { start() }
}
@@ -515,6 +558,7 @@
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 @@
startState,
endState,
startVelocity,
+ startFrameTime,
windowBackgroundLayer,
transitionContainer,
transitionContainerOverlay,
@@ -722,6 +767,7 @@
startState: State,
endState: State,
startVelocity: PointF,
+ startFrameTime: Long,
windowBackgroundLayer: GradientDrawable,
transitionContainer: View,
transitionContainerOverlay: ViewGroupOverlay,
@@ -912,7 +958,7 @@
}
}
- 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 2f83d82..92b6fd4 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.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 @@
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 @@
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 @@
"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 @@
}
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 58ad363..79f1907 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 @@
)
.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 @@
injectStub,
qsContext,
shadeDisplayAwareStub,
+ applicationStub,
+ globalConfigStub,
configStateStub,
configControllerStub,
configInteractorStub,
@@ -259,6 +281,60 @@
}
@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 @@
}
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 0000000..9fe85b7
--- /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 0000000..6e91727
--- /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 0000000..fd3902f
--- /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 4705d8d..beaf963 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.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 @@
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 @@
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 778d7e7..3926b32 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
@@ -24,6 +24,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
+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
@@ -59,7 +60,7 @@
Box(modifier = Modifier.fillMaxSize()) {
with(communalPopupSection) { Popup() }
with(ambientStatusBarSection) {
- AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+ AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
}
CommunalHub(
viewModel = viewModel,
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 4f1acdc..6591a75 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.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 @@
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 d483f88..9f582bc 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.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 @@
@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 2d32fd7..f7ce215 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 @@
/** 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 3fce890..b1a1945 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.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.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 @@
// 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 @@
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 @@
.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 Dimensions {
val Padding = 16.dp
val BrightnessSliderHeight = 64.dp
- val GridMaxHeight = 800.dp
+ val GridMaxHeight = 420.dp
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index 6d03118..0e35e1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -45,6 +45,7 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
@@ -56,7 +57,7 @@
/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
class ButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit
+ private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -84,14 +85,26 @@
},
color =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.tertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.tertiaryContainer
+ }
} else {
- MaterialTheme.colorScheme.surface
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.surfaceContainerHigh
+ } else {
+ MaterialTheme.colorScheme.surface
+ }
},
shape = RoundedCornerShape(20.dp),
contentColor =
if (viewModel.isActive) {
- MaterialTheme.colorScheme.onTertiaryContainer
+ if (Flags.volumeRedesign()) {
+ MaterialTheme.colorScheme.onPrimary
+ } else {
+ MaterialTheme.colorScheme.onTertiaryContainer
+ }
} else {
MaterialTheme.colorScheme.onSurface
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index bb2daec..2cd7304 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -42,6 +42,7 @@
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
@@ -51,7 +52,7 @@
/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
private val viewModelFlow: StateFlow<ButtonViewModel?>,
- private val onCheckedChange: (isChecked: Boolean) -> Unit
+ private val onCheckedChange: (isChecked: Boolean) -> Unit,
) : ComposeVolumePanelUiComponent {
@Composable
@@ -68,15 +69,29 @@
BottomComponentButtonSurface {
val colors =
if (viewModel.isActive) {
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ )
+ }
} else {
- ButtonDefaults.buttonColors(
- containerColor = Color.Transparent,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- )
+ if (Flags.volumeRedesign()) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ contentColor = MaterialTheme.colorScheme.onSurface,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
}
Button(
modifier =
@@ -93,7 +108,7 @@
onClick = { onCheckedChange(!viewModel.isActive) },
shape = RoundedCornerShape(20.dp),
colors = colors,
- contentPadding = PaddingValues(0.dp)
+ contentPadding = PaddingValues(0.dp),
) {
Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 581fb9d..25892c5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -37,11 +37,13 @@
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
@@ -51,8 +53,11 @@
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.PlatformIconButton
import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -84,7 +89,11 @@
val sliderPadding by topSliderPadding(isExpandable)
VolumeSlider(
- modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(),
+ modifier =
+ Modifier.thenIf(!Flags.volumeRedesign()) {
+ Modifier.padding(end = { sliderPadding.roundToPx() })
+ }
+ .fillMaxWidth(),
state = sliderState,
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
@@ -93,15 +102,29 @@
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
+ button =
+ if (Flags.volumeRedesign()) {
+ {
+ ExpandButton(
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ )
+ }
+ } else {
+ null
+ },
)
- ExpandButton(
- modifier = Modifier.align(Alignment.CenterEnd),
- isExpanded = isExpanded,
- isExpandable = isExpandable,
- onExpandedChanged = onExpandedChanged,
- sliderColors = sliderColors,
- )
+ if (!Flags.volumeRedesign()) {
+ ExpandButtonLegacy(
+ modifier = Modifier.align(Alignment.CenterEnd),
+ isExpanded = isExpanded,
+ isExpandable = isExpandable,
+ onExpandedChanged = onExpandedChanged,
+ sliderColors = sliderColors,
+ )
+ }
}
AnimatedVisibility(
visible = isExpanded || !isExpandable,
@@ -153,7 +176,7 @@
}
@Composable
-private fun ExpandButton(
+private fun ExpandButtonLegacy(
isExpanded: Boolean,
isExpandable: Boolean,
onExpandedChanged: (Boolean) -> Unit,
@@ -200,6 +223,48 @@
}
}
+@Composable
+private fun ExpandButton(
+ isExpanded: Boolean,
+ isExpandable: Boolean,
+ onExpandedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val expandButtonStateDescription =
+ if (isExpanded) {
+ stringResource(R.string.volume_panel_expanded_sliders)
+ } else {
+ stringResource(R.string.volume_panel_collapsed_sliders)
+ }
+ AnimatedVisibility(
+ modifier = modifier,
+ visible = isExpandable,
+ enter = expandButtonEnterTransition(),
+ exit = expandButtonExitTransition(),
+ ) {
+ PlatformIconButton(
+ modifier =
+ Modifier.size(width = 48.dp, height = 40.dp).semantics {
+ role = Role.Switch
+ stateDescription = expandButtonStateDescription
+ },
+ onClick = { onExpandedChanged(!isExpanded) },
+ colors =
+ IconButtonDefaults.iconButtonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ ),
+ iconResource =
+ if (isExpanded) {
+ R.drawable.ic_arrow_down_24dp
+ } else {
+ R.drawable.ic_arrow_up_24dp
+ },
+ contentDescription = null,
+ )
+ }
+}
+
private fun enterTransition(index: Int, totalCount: Int): EnterTransition {
val enterDelay = ((totalCount - index + 1) * 10).coerceAtLeast(0)
val enterDuration = (EXPAND_DURATION_MILLIS - enterDelay).coerceAtLeast(100)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 97ce429..fa5f72b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -24,9 +24,18 @@
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Slider
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
@@ -48,6 +57,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
+import com.android.systemui.Flags
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -61,11 +71,104 @@
fun VolumeSlider(
state: SliderState,
onValueChange: (newValue: Float) -> Unit,
- onValueChangeFinished: (() -> Unit)? = null,
onIconTapped: () -> Unit,
+ sliderColors: PlatformSliderColors,
modifier: Modifier = Modifier,
+ hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ onValueChangeFinished: (() -> Unit)? = null,
+ button: (@Composable () -> Unit)? = null,
+) {
+ if (!Flags.volumeRedesign()) {
+ LegacyVolumeSlider(
+ state = state,
+ onValueChange = onValueChange,
+ onIconTapped = onIconTapped,
+ sliderColors = sliderColors,
+ onValueChangeFinished = onValueChangeFinished,
+ modifier = modifier,
+ hapticsViewModelFactory = hapticsViewModelFactory,
+ )
+ return
+ }
+
+ val value by valueState(state)
+ Column(modifier) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ state.icon?.let {
+ Icon(
+ icon = it,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.size(40.dp).padding(8.dp),
+ )
+ }
+ Text(
+ text = state.label,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier.weight(1f).align(Alignment.CenterVertically),
+ )
+ button?.invoke()
+ }
+ Slider(
+ value = value,
+ valueRange = state.valueRange,
+ onValueChange = onValueChange,
+ onValueChangeFinished = onValueChangeFinished,
+ enabled = state.isEnabled,
+ modifier =
+ Modifier.height(40.dp).sysuiResTag(state.label).clearAndSetSemantics {
+ if (state.isEnabled) {
+ contentDescription = state.label
+ state.a11yClickDescription?.let {
+ customActions =
+ listOf(
+ CustomAccessibilityAction(it) {
+ onIconTapped()
+ true
+ }
+ )
+ }
+
+ state.a11yStateDescription?.let { stateDescription = it }
+ progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange)
+ } else {
+ disabled()
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
+ }
+ setProgress { targetValue ->
+ val targetDirection =
+ when {
+ targetValue > value -> 1
+ targetValue < value -> -1
+ else -> 0
+ }
+
+ val newValue =
+ (value + targetDirection * state.a11yStep).coerceIn(
+ state.valueRange.start,
+ state.valueRange.endInclusive,
+ )
+ onValueChange(newValue)
+ true
+ }
+ },
+ )
+ }
+}
+
+@Composable
+private fun LegacyVolumeSlider(
+ state: SliderState,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
sliderColors: PlatformSliderColors,
hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
+ modifier: Modifier = Modifier,
+ onValueChangeFinished: (() -> Unit)? = null,
) {
val value by valueState(state)
val interactionSource = remember { MutableInteractionSource() }
@@ -178,7 +281,7 @@
val shouldSkipAnimation =
prevState is SliderState.Empty || prevState.isEnabled != state.isEnabled
val value =
- if (shouldSkipAnimation) mutableFloatStateOf(state.value)
+ if (shouldSkipAnimation) remember { mutableFloatStateOf(state.value) }
else animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
prevState = state
return value
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
index 4ae4eb8..28226ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSliderContent.kt
@@ -53,7 +53,7 @@
DisabledMessage,
}
-/** Shows label of the [VolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
+/** Shows label of the [LegacyVolumeSlider]. Also shows [disabledMessage] when not [isEnabled]. */
@Composable
fun VolumeSliderContent(
label: String,
@@ -89,7 +89,7 @@
}
}
},
- measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled)
+ measurePolicy = VolumeSliderContentMeasurePolicy(isEnabled),
)
}
@@ -102,7 +102,7 @@
override fun MeasureScope.measure(
measurables: List<Measurable>,
- constraints: Constraints
+ constraints: Constraints,
): MeasureResult {
val labelPlaceable =
measurables
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 0fc88b2..a4237f3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -225,7 +225,7 @@
return animateElementValueAsState(value, key, SharedColorType, canOverflow = false)
}
-private object SharedColorType : SharedValueType<Color, ColorDelta> {
+internal object SharedColorType : SharedValueType<Color, ColorDelta> {
override val unspecifiedValue: Color = Color.Unspecified
override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f)
@@ -255,17 +255,17 @@
alpha = aOklab.alpha + b.alpha * bWeight,
colorSpace = ColorSpaces.Oklab,
)
- .convert(aOklab.colorSpace)
+ .convert(a.colorSpace)
}
}
/**
- * Represents the diff between two colors in the same color space.
+ * Represents the diff between two colors in the Oklab color space.
*
* Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor
* is internal.
*/
-private class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
+internal class ColorDelta(val red: Float, val green: Float, val blue: Float, val alpha: Float)
@Composable
internal fun <T> animateSharedValueAsState(
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 caf5e41..2d589f3 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.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 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 @@
* @return the consumed delta
*/
override fun onDrag(delta: Float): Float {
- return onDrag(delta, swipeAnimation)
- }
-
- private fun <T : ContentKey> onDrag(delta: Float, swipeAnimation: SwipeAnimation<T>): Float {
- if (delta == 0f || !isDrivingTransition || swipeAnimation.isAnimatingOffset()) {
+ 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)
+ }
- val distance = swipeAnimation.distance()
- val previousOffset = swipeAnimation.dragOffset
+ 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)
+ }
+
+ 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 @@
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 a14b2b3..bf7e8e8 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.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 @@
@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 bdc1461..d7bac14 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.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 @@
_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 @@
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 ae235e5..35cdf81 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 @@
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 8c4cd8c..152f05e 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.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 @@
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 0000000..2233deb
--- /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 0000000..f459c46
--- /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 a5be4dc..98a0017 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 @@
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/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 3644b30..2fd1d8d8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -18,7 +18,10 @@
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -495,4 +498,13 @@
assertThat(lastValues[SceneA]).isWithin(0.001f).of(100f)
assertThat(lastValues[SceneB]).isWithin(0.001f).of(100f)
}
+
+ @Test
+ fun interpolatedColor() {
+ val a = Color.Red
+ val b = Color.Green
+ val delta = SharedColorType.diff(b, a) // b - a
+ val interpolated = SharedColorType.addWeighted(a, delta, 0.5f) // a + (b - a) * 0.5f
+ rule.setContent { Box(Modifier.fillMaxSize().background(interpolated)) }
+ }
}
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 a301856..f1da01f 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.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.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.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.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 @@
}
@Test
- fun elementTransitionDuringOverscroll() {
+ fun elementTransitionDuringOverscrollWithOverscrollDSL() {
val layoutWidth = 200.dp
val layoutHeight = 400.dp
val overscrollTranslateY = 10.dp
@@ -765,6 +769,241 @@
assertThat(animatedFloat).isEqualTo(100f)
}
+ private fun expectedOffset(currentOffset: Dp, density: Density): Dp {
+ return with(density) {
+ OffsetOverscrollEffect.computeOffset(this, 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 0000000..d267cc5
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/effect/OffsetOverscrollEffectTest.kt
@@ -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 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 org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class OffsetOverscrollEffectTest {
+ @get:Rule val rule = createComposeRule()
+
+ private fun expectedOffset(currentOffset: Dp, density: Density): Dp {
+ return with(density) {
+ OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp()
+ }
+ }
+
+ @Test
+ fun applyVerticalOffset_duringVerticalOverscroll() {
+ // 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
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+ }
+
+ @Test
+ fun applyNoOffset_duringHorizontalOverscroll() {
+ // 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 layoutSize = 200.dp
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Horizontal,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(touchSlop + layoutSize.toPx(), 0f), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun backToZero_afterOverscroll() {
+ // 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
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+
+ rule.onRoot().performTouchInput { up() }
+
+ onBox.assertTopPositionInRootIsEqualTo(0.dp)
+ }
+
+ @Test
+ fun offsetOverscroll_followTheTouchPointer() {
+ // 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
+ lateinit var density: Density
+ val layoutSize = 200.dp
+
+ rule.setContent {
+ density = LocalDensity.current
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ val overscrollEffect = rememberOffsetOverscrollEffect(Orientation.Vertical)
+
+ Box(
+ Modifier.overscroll(overscrollEffect)
+ // A scrollable that does not consume the scroll gesture.
+ .scrollable(
+ state = rememberScrollableState { 0f },
+ orientation = Orientation.Vertical,
+ overscrollEffect = overscrollEffect,
+ )
+ .size(layoutSize)
+ .testTag("box")
+ )
+ }
+
+ val onBox = rule.onNodeWithTag("box")
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ // A full screen scroll.
+ moveBy(Offset(0f, touchSlop + layoutSize.toPx()), delayMillis = 1_000)
+ }
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize, density))
+
+ rule.onRoot().performTouchInput {
+ // Reduced by half.
+ moveBy(Offset(0f, -layoutSize.toPx() / 2), delayMillis = 1_000)
+ }
+ onBox.assertTopPositionInRootIsEqualTo(expectedOffset(layoutSize / 2, density))
+ }
+}
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 e27f9b5..c8fb2cb 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 @@
}
@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 00b0c44..43131b1 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/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 9d471f4..ad12c61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -139,13 +139,11 @@
private ActivityInfo mActivityInfo;
@Mock
private Drawable mDrawable;
- @Mock
- private HearingDevicesPresetsController mPresetsController;
+
private SystemUIDialog mDialog;
private SystemUIDialog.Factory mDialogFactory;
private HearingDevicesDialogDelegate mDialogDelegate;
private TestableLooper mTestableLooper;
- private final List<CachedBluetoothDevice> mDevices = new ArrayList<>();
@Before
public void setUp() {
@@ -155,7 +153,7 @@
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mLocalBluetoothAdapter.isEnabled()).thenReturn(true);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
- when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
+ when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(List.of(mCachedDevice));
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
@@ -163,6 +161,7 @@
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
@@ -170,12 +169,11 @@
when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
mContext.setMockPackageManager(mPackageManager);
- mDevices.add(mCachedDevice);
}
@Test
public void clickPairNewDeviceButton_intentActionMatch() {
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithPairNewDeviceButton();
mDialog.show();
getPairNewDeviceButton(mDialog).performClick();
@@ -191,7 +189,7 @@
@Test
public void onDeviceItemGearClicked_intentActionMatch() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialogDelegate.onDeviceItemGearClicked(mHearingDeviceItem, new View(mContext));
@@ -206,7 +204,7 @@
@Test
public void onDeviceItemOnClicked_connectedDevice_disconnect() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
@@ -222,7 +220,7 @@
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(0);
@@ -237,7 +235,7 @@
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(1);
@@ -247,9 +245,8 @@
@EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools()
throws PackageManager.NameNotFoundException {
- when(mPackageManager.queryIntentActivities(
- eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
- List.of(new ResolveInfo()));
+ when(mPackageManager.queryIntentActivities(eq(LIVE_CAPTION_INTENT), anyInt()))
+ .thenReturn(List.of(new ResolveInfo()));
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsHearingDevicesRelatedToolName,
new String[]{TEST_PKG + "/" + TEST_CLS});
@@ -260,18 +257,18 @@
when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT);
when(mDrawable.mutate()).thenReturn(mDrawable);
- setUpPairNewDeviceDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
assertToolsUi(2);
}
@Test
- public void showDialog_noPreset_presetGone() {
- when(mPresetsController.getAllPresetInfo()).thenReturn(new ArrayList<>());
- when(mPresetsController.getActivePresetIndex()).thenReturn(PRESET_INDEX_UNAVAILABLE);
+ public void showDialog_noPreset_presetLayoutGone() {
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(new ArrayList<>());
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(PRESET_INDEX_UNAVAILABLE);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
ViewGroup presetLayout = getPresetLayout(mDialog);
@@ -281,11 +278,12 @@
@Test
public void showDialog_presetExist_presetSelected() {
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
+ mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
@@ -295,48 +293,32 @@
@Test
public void onActiveDeviceChanged_presetExist_presetSelected() {
- setUpDeviceListDialog();
+ setUpDeviceDialogWithoutPairNewDeviceButton();
mDialog.show();
BluetoothHapPresetInfo info = getTestPresetInfo();
- when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
- when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(List.of(info));
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+
+ Spinner spinner = getPresetSpinner(mDialog);
+ assertThat(spinner.getSelectedItemPosition()).isEqualTo(-1);
mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
mTestableLooper.processAllMessages();
ViewGroup presetLayout = getPresetLayout(mDialog);
assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
- Spinner spinner = getPresetSpinner(mDialog);
assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
}
-
-
- private void setUpPairNewDeviceDialog() {
- mDialogFactory = new SystemUIDialog.Factory(
- mContext,
- mSystemUIDialogManager,
- mSysUiState,
- getFakeBroadcastDispatcher(),
- mDialogTransitionAnimator
- );
- mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- true,
- TEST_LAUNCH_SOURCE_ID,
- mDialogFactory,
- mActivityStarter,
- mDialogTransitionAnimator,
- mLocalBluetoothManager,
- new Handler(mTestableLooper.getLooper()),
- mAudioManager,
- mUiEventLogger
- );
-
- mDialog = mDialogDelegate.createDialog();
+ private void setUpDeviceDialogWithPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ true);
}
- private void setUpDeviceListDialog() {
+ private void setUpDeviceDialogWithoutPairNewDeviceButton() {
+ setUpDeviceDialog(/* showPairNewDevice= */ false);
+ }
+
+ private void setUpDeviceDialog(boolean showPairNewDevice) {
mDialogFactory = new SystemUIDialog.Factory(
mContext,
mSystemUIDialogManager,
@@ -345,8 +327,7 @@
mDialogTransitionAnimator
);
mDialogDelegate = new HearingDevicesDialogDelegate(
- mContext,
- false,
+ showPairNewDevice,
TEST_LAUNCH_SOURCE_ID,
mDialogFactory,
mActivityStarter,
@@ -356,15 +337,14 @@
mAudioManager,
mUiEventLogger
);
-
mDialog = mDialogDelegate.createDialog();
- mDialogDelegate.setHearingDevicesPresetsController(mPresetsController);
}
private BluetoothHapPresetInfo getTestPresetInfo() {
BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
when(info.getName()).thenReturn(TEST_PRESET_NAME);
when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+ when(info.isAvailable()).thenReturn(true);
return info;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
index 2ac5d10..c9779c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsControllerTest.java
@@ -21,10 +21,10 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.never;
import static java.util.Collections.emptyList;
@@ -39,7 +39,6 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
-import com.android.settingslib.bluetooth.LocalBluetoothProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +52,7 @@
import org.mockito.junit.MockitoRule;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.Executor;
/** Tests for {@link HearingDevicesPresetsController}. */
@@ -62,6 +62,7 @@
public class HearingDevicesPresetsControllerTest extends SysuiTestCase {
private static final int TEST_PRESET_INDEX = 1;
+ private static final int TEST_UPDATED_PRESET_INDEX = 2;
private static final String TEST_PRESET_NAME = "test_preset";
private static final int TEST_HAP_GROUP_ID = 1;
private static final int TEST_REASON = 1024;
@@ -74,14 +75,13 @@
@Mock
private HapClientProfile mHapClientProfile;
@Mock
- private CachedBluetoothDevice mCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedDevice;
@Mock
- private CachedBluetoothDevice mSubCachedBluetoothDevice;
+ private CachedBluetoothDevice mCachedMemberDevice;
@Mock
- private BluetoothDevice mBluetoothDevice;
+ private BluetoothDevice mDevice;
@Mock
- private BluetoothDevice mSubBluetoothDevice;
-
+ private BluetoothDevice mMemberDevice;
@Mock
private HearingDevicesPresetsController.PresetCallback mCallback;
@@ -91,15 +91,19 @@
public void setUp() {
when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mHapClientProfile.isProfileReady()).thenReturn(true);
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
- when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
- when(mSubCachedBluetoothDevice.getDevice()).thenReturn(mSubBluetoothDevice);
+ when(mCachedDevice.getDevice()).thenReturn(mDevice);
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
+ when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
+ when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
mController = new HearingDevicesPresetsController(mProfileManager, mCallback);
+ mController.setDevice(mCachedDevice);
}
@Test
public void onServiceConnected_callExpectedCallback() {
+ preparePresetInfo(/* isValid= */ true);
+
mController.onServiceConnected();
verify(mHapClientProfile).registerCallback(any(Executor.class),
@@ -108,115 +112,129 @@
}
@Test
- public void getAllPresetInfo_setInvalidHearingDevice_getEmpty() {
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(emptyList());
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void setDevice_nonHapDevice_getEmptyListAndInvalidActiveIndex() {
+ when(mCachedDevice.getProfiles()).thenReturn(emptyList());
+ preparePresetInfo(/* isValid= */ true);
+
+ mController.setDevice(mCachedDevice);
+
+ assertThat(mController.getAllPresetInfo()).isEmpty();
+ assertThat(mController.getActivePresetIndex()).isEqualTo(
+ BluetoothHapClient.PRESET_INDEX_UNAVAILABLE);
+ }
+
+ @Test
+ public void refreshPresetInfo_containsOnlyNotAvailablePresetInfo_getEmptyList() {
+ preparePresetInfo(/* isValid= */ false);
+
+ mController.refreshPresetInfo();
assertThat(mController.getAllPresetInfo()).isEmpty();
}
@Test
- public void getAllPresetInfo_containsNotAvailablePresetInfo_getEmpty() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(false);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- assertThat(mController.getAllPresetInfo()).isEmpty();
+ mController.refreshPresetInfo();
+
+ List<BluetoothHapPresetInfo> presetInfos = mController.getAllPresetInfo();
+ assertThat(presetInfos.size()).isEqualTo(1);
+ assertThat(presetInfos).contains(infos.getFirst());
}
@Test
- public void getAllPresetInfo_containsOnePresetInfo_getOnePresetInfo() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
+ public void refreshPresetInfo_getExpectedIndex() {
+ preparePresetInfo(/* isValid= */ true);
- assertThat(mController.getAllPresetInfo()).contains(hapPresetInfo);
- }
-
- @Test
- public void getActivePresetIndex_getExpectedIndex() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ mController.refreshPresetInfo();
assertThat(mController.getActivePresetIndex()).isEqualTo(TEST_PRESET_INDEX);
}
@Test
- public void onPresetSelected_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void refreshPresetInfo_callbackIsCalledWhenNeeded() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetSelected(mBluetoothDevice, TEST_PRESET_INDEX, TEST_REASON);
+ mController.refreshPresetInfo();
- verify(mCallback).onPresetInfoUpdated(eq(List.of(hapPresetInfo)), eq(TEST_PRESET_INDEX));
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
+
+ Mockito.reset(mCallback);
+ mController.refreshPresetInfo();
+
+ verify(mCallback, never()).onPresetInfoUpdated(anyList(), anyInt());
+
+ Mockito.reset(mCallback);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_UPDATED_PRESET_INDEX);
+ mController.refreshPresetInfo();
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_UPDATED_PRESET_INDEX);
}
@Test
- public void onPresetInfoChanged_presetIndex_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
- setValidHearingDeviceSupportHap();
- BluetoothHapPresetInfo hapPresetInfo = getHapPresetInfo(true);
- when(mHapClientProfile.getAllPresetInfo(mBluetoothDevice)).thenReturn(
- List.of(hapPresetInfo));
- when(mHapClientProfile.getActivePresetIndex(mBluetoothDevice)).thenReturn(
- TEST_PRESET_INDEX);
+ public void onPresetSelected_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
- mController.onPresetInfoChanged(mBluetoothDevice, List.of(hapPresetInfo), TEST_REASON);
+ mController.onPresetSelected(mDevice, TEST_PRESET_INDEX, TEST_REASON);
- verify(mCallback).onPresetInfoUpdated(List.of(hapPresetInfo), TEST_PRESET_INDEX);
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
+ }
+
+ @Test
+ public void onPresetInfoChanged_callOnPresetInfoUpdatedWithExpectedPresetIndex() {
+ List<BluetoothHapPresetInfo> infos = preparePresetInfo(/* isValid= */ true);
+
+ mController.onPresetInfoChanged(mDevice, infos, TEST_REASON);
+
+ verify(mCallback).onPresetInfoUpdated(infos, TEST_PRESET_INDEX);
}
@Test
public void onPresetSelectionFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onPresetSelectionFailed(mBluetoothDevice, TEST_REASON);
+ mController.onPresetSelectionFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
public void onSetPresetNameFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
- mController.onSetPresetNameFailed(mBluetoothDevice, TEST_REASON);
+ mController.onSetPresetNameFailed(mDevice, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
- public void onPresetSelectionForGroupFailed_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
+ public void onPresetSelectionForGroupFailed_callSelectPresetIndependently() {
mController.selectPreset(TEST_PRESET_INDEX);
Mockito.reset(mHapClientProfile);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
-
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
public void onSetPresetNameForGroupFailed_callOnPresetCommandFailed() {
- setValidHearingDeviceSupportHap();
-
mController.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_REASON);
verify(mCallback).onPresetCommandFailed(TEST_REASON);
}
@Test
+ public void registerHapCallback_profileNotReady_addServiceListener() {
+ when(mHapClientProfile.isProfileReady()).thenReturn(false);
+
+ mController.registerHapCallback();
+
+ verify(mProfileManager).addServiceListener(mController);
+ verify(mHapClientProfile, never()).registerCallback(any(Executor.class),
+ any(BluetoothHapClient.Callback.class));
+ }
+
+ @Test
public void registerHapCallback_callHapRegisterCallback() {
mController.registerHapCallback();
@@ -233,9 +251,8 @@
@Test
public void selectPreset_supportSynchronized_validGroupId_callSelectPresetForGroup() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
@@ -243,28 +260,34 @@
}
@Test
- public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(true);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(
+ public void selectPreset_supportSynchronized_invalidGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(true);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
}
@Test
- public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndividual() {
- setValidHearingDeviceSupportHap();
- when(mHapClientProfile.supportsSynchronizedPresets(mBluetoothDevice)).thenReturn(false);
- when(mHapClientProfile.getHapGroup(mBluetoothDevice)).thenReturn(TEST_HAP_GROUP_ID);
+ public void selectPreset_notSupportSynchronized_validGroupId_callSelectPresetIndependently() {
+ when(mHapClientProfile.supportsSynchronizedPresets(mDevice)).thenReturn(false);
+ when(mHapClientProfile.getHapGroup(mDevice)).thenReturn(TEST_HAP_GROUP_ID);
mController.selectPreset(TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mBluetoothDevice, TEST_PRESET_INDEX);
- verify(mHapClientProfile).selectPreset(mSubBluetoothDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mDevice, TEST_PRESET_INDEX);
+ verify(mHapClientProfile).selectPreset(mMemberDevice, TEST_PRESET_INDEX);
+ }
+
+ private List<BluetoothHapPresetInfo> preparePresetInfo(boolean isValid) {
+ BluetoothHapPresetInfo info = getHapPresetInfo(isValid);
+ List<BluetoothHapPresetInfo> infos = List.of(info);
+ when(mHapClientProfile.getAllPresetInfo(mDevice)).thenReturn(infos);
+ when(mHapClientProfile.getActivePresetIndex(mDevice)).thenReturn(TEST_PRESET_INDEX);
+ return infos;
}
private BluetoothHapPresetInfo getHapPresetInfo(boolean available) {
@@ -274,12 +297,4 @@
when(info.isAvailable()).thenReturn(available);
return info;
}
-
- private void setValidHearingDeviceSupportHap() {
- LocalBluetoothProfile hapClientProfile = mock(HapClientProfile.class);
- List<LocalBluetoothProfile> profiles = List.of(hapClientProfile);
- when(mCachedBluetoothDevice.getProfiles()).thenReturn(profiles);
-
- mController.setHearingDeviceIfSupportHap(mCachedBluetoothDevice);
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 4e64c50..297aee5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.domain.interactor
import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.MotionEvent
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -24,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -39,6 +42,8 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
@@ -57,6 +62,8 @@
private val testScope: TestScope = kosmos.testScope
private val authController: AuthController = kosmos.authController
+ private val fingerprintManager: FingerprintManager = kosmos.fingerprintManager
+ @Mock private lateinit var fingerprintSensorProperties: FingerprintSensorPropertiesInternal
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@Mock private lateinit var udfpsOverlayParams: UdfpsOverlayParams
@@ -122,6 +129,20 @@
context.orCreateTestableResources.removeOverride(R.dimen.pixel_pitch)
}
+ @Test
+ fun testSetIgnoreDisplayTouches() =
+ testScope.runTest {
+ createUdfpsOverlayInteractor()
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ whenever(authController.udfpsProps).thenReturn(listOf(fingerprintSensorProperties))
+
+ underTest.setHandleTouches(false)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(true))
+
+ underTest.setHandleTouches(true)
+ verify(fingerprintManager).setIgnoreDisplayTouches(anyLong(), anyInt(), eq(false))
+ }
+
private fun createUdfpsOverlayInteractor() {
underTest = kosmos.udfpsOverlayInteractor
testScope.runCurrent()
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 790df03..1e937b4 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.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 @@
@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 @@
monitor,
packageManager,
dreamManager,
+ kosmos.communalSettingsInteractor,
logBuffer,
)
@@ -117,7 +123,7 @@
/** 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 @@
/** 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 @@
* enabled.
*/
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
fun testDisabledForMobileWithoutMobileHub() {
start()
@@ -154,8 +162,9 @@
/** 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 f924ccb..b07097d 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.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.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 @@
mKeyguardUpdateMonitor,
mScrimManager,
mCommunalInteractor,
+ kosmos.communalSettingsInteractor,
kosmos.sceneInteractor,
mSystemDialogsCloser,
mUiEventLogger,
@@ -1283,7 +1286,7 @@
environmentComponents.verifyNoMoreInteractions()
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun testAmbientTouchHandlersRegistration_registerHideComplicationAndCommunal() {
val client = client
@@ -1303,9 +1306,11 @@
.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 76434ee..1de38ee 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 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 @@
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 @@
mock<InputDeviceTutorialLogger>(),
SavedStateHandle(mapOf(INTENT_TUTORIAL_SCOPE_KEY to scope)),
)
- lifecycle.addObserver(viewModel)
return viewModel
}
@@ -279,7 +272,7 @@
collectValues(viewModel.screen) // just to initialize viewModel
peripheralsState(touchpadConnected = true)
- lifecycle.handleLifecycleEvent(Event.ON_START)
+ viewModel.onStart(TestLifecycleOwner())
assertGesturesDisabled()
}
@@ -291,8 +284,8 @@
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 0000000..f78c692
--- /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 75190e9..92c76ff 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 @@
),
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 @@
),
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 77c615c..789b10b 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.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 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 @@
@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 @@
context = context,
communalInteractor = kosmos.communalInteractor,
communalSceneRepository = kosmos.communalSceneRepository,
+ communalSettingsInteractor = kosmos.communalSettingsInteractor,
sceneInteractor = kosmos.sceneInteractor,
)
}
@@ -76,28 +77,30 @@
@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 @@
@Test
fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() =
testScope.runTest {
- kosmos.setCommunalEnabled(false)
+ kosmos.setCommunalV2Enabled(false)
runCurrent()
assertThat(
@@ -143,7 +146,8 @@
@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 e079619..635f8026 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 @@
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 @@
)
}
+ @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 111b3b6..4457d9b 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.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 @@
}
}
+ @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/customize/TileQueryHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
index 09a6c2c..1899b7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -44,6 +44,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -384,6 +385,7 @@
return mSpec;
}
+ @NonNull
@Override
public State getState() {
return mState;
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 0000000..eef195b
--- /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 0000000..b591538
--- /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 a9a527f..4891c9f 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.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
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
index f2bfd72..a8e390c 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.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 7a99aef..3f4a134 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
@@ -31,8 +31,10 @@
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.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.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
@@ -55,33 +57,22 @@
@SmallTest
class AirplaneModeTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mHost: QSHost
- @Mock
- private lateinit var mMetricsLogger: MetricsLogger
- @Mock
- private lateinit var mStatusBarStateController: StatusBarStateController
- @Mock
- private lateinit var mActivityStarter: ActivityStarter
- @Mock
- private lateinit var mQsLogger: QSLogger
- @Mock
- private lateinit var mBroadcastDispatcher: BroadcastDispatcher
- @Mock
- private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
- @Mock
- private lateinit var mConnectivityManager: ConnectivityManager
- @Mock
- private lateinit var mGlobalSettings: GlobalSettings
- @Mock
- private lateinit var mUserTracker: UserTracker
- @Mock
- private lateinit var mUiEventLogger: QsEventLogger
+ @Mock private lateinit var mHost: QSHost
+ @Mock private lateinit var mMetricsLogger: MetricsLogger
+ @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+ @Mock private lateinit var mActivityStarter: ActivityStarter
+ @Mock private lateinit var mQsLogger: QSLogger
+ @Mock private lateinit var mBroadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager>
+ @Mock private lateinit var mConnectivityManager: ConnectivityManager
+ @Mock private lateinit var mGlobalSettings: GlobalSettings
+ @Mock private lateinit var mUserTracker: UserTracker
+ @Mock private lateinit var mUiEventLogger: QsEventLogger
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
- @Mock
- private lateinit var mClickJob: Job
+ @Mock private lateinit var mClickJob: Job
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -89,20 +80,22 @@
Mockito.`when`(mHost.context).thenReturn(mContext)
Mockito.`when`(mHost.userContext).thenReturn(mContext)
Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager)
- mTile = AirplaneModeTile(
- mHost,
- mUiEventLogger,
- mTestableLooper.looper,
- Handler(mTestableLooper.looper),
- FalsingManagerFake(),
- mMetricsLogger,
- mStatusBarStateController,
- mActivityStarter,
- mQsLogger,
- mBroadcastDispatcher,
- mLazyConnectivityManager,
- mGlobalSettings,
- mUserTracker)
+ mTile =
+ AirplaneModeTile(
+ mHost,
+ mUiEventLogger,
+ mTestableLooper.looper,
+ Handler(mTestableLooper.looper),
+ FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQsLogger,
+ mBroadcastDispatcher,
+ mLazyConnectivityManager,
+ mGlobalSettings,
+ mUserTracker,
+ )
}
@After
@@ -117,8 +110,7 @@
mTile.handleUpdateState(state, 0)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_off))
}
@Test
@@ -127,8 +119,7 @@
mTile.handleUpdateState(state, 1)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_airplane_icon_on))
}
@Test
@@ -150,4 +141,12 @@
verify(mConnectivityManager, times(0)).setAirplaneMode(any())
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index d6be314..ae00349 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
@@ -31,8 +31,10 @@
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.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.BatteryController
import com.android.systemui.util.settings.FakeSettings
@@ -94,7 +96,7 @@
activityStarter,
qsLogger,
batteryController,
- secureSettings
+ secureSettings,
)
tile.initialize()
@@ -150,8 +152,7 @@
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_icon_off))
}
@Test
@@ -161,7 +162,14 @@
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_battery_saver_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/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 093cdf2..31519c5 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
@@ -23,7 +23,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@
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.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.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,24 +56,15 @@
const val CAMERA_TOGGLE_DISABLED: Boolean = true
}
- @Mock
- private lateinit var host: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var privacyController: IndividualSensorPrivacyController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var uiEventLogger: QsEventLoggerFake
- @Mock
- private lateinit var safetyCenterManager: SafetyCenterManager
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var uiEventLogger: QsEventLoggerFake
+ @Mock private lateinit var safetyCenterManager: SafetyCenterManager
private lateinit var testableLooper: TestableLooper
private lateinit var tile: CameraToggleTile
@@ -82,7 +75,8 @@
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- tile = CameraToggleTile(
+ tile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -94,7 +88,8 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
}
@After
@@ -109,8 +104,7 @@
tile.handleUpdateState(state, CAMERA_TOGGLE_ENABLED)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_on))
}
@Test
@@ -119,14 +113,14 @@
tile.handleUpdateState(state, CAMERA_TOGGLE_DISABLED)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_camera_access_icon_off))
}
@Test
fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- val cameraTile = CameraToggleTile(
+ val cameraTile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -138,7 +132,8 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
cameraTile.destroy()
testableLooper.processAllMessages()
@@ -147,7 +142,8 @@
@Test
fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- val cameraTile = CameraToggleTile(
+ val cameraTile =
+ CameraToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -159,9 +155,18 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
cameraTile.destroy()
testableLooper.processAllMessages()
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index 1343527..2c796a9 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
@@ -39,6 +39,7 @@
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;
@@ -133,7 +134,7 @@
mTile.handleUpdateState(state, COLOR_INVERSION_DISABLED);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_off));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_icon_off));
}
@Test
@@ -143,6 +144,14 @@
mTile.handleUpdateState(state, COLOR_INVERSION_ENABLED);
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_invert_colors_icon_on));
+ .isEqualTo(createExpectedIcon(R.drawable.qs_invert_colors_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/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index 73ae4ee..23be9da 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
@@ -29,8 +29,10 @@
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.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.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DataSaverController
@@ -84,7 +86,7 @@
mQsLogger,
dataSaverController,
mDialogTransitionAnimator,
- systemUIDialogFactory
+ systemUIDialogFactory,
)
}
@@ -100,8 +102,7 @@
tile.handleUpdateState(state, true)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_on))
}
@Test
@@ -110,7 +111,14 @@
tile.handleUpdateState(state, false)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_data_saver_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_data_saver_icon_off))
+ }
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index f90463e..3cb9091 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
@@ -6,7 +6,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -14,8 +13,11 @@
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.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.FlashlightController
import com.google.common.truth.Truth
import org.junit.After
@@ -69,7 +71,7 @@
statusBarStateController,
activityStarter,
qsLogger,
- flashlightController
+ flashlightController,
)
}
@@ -87,8 +89,7 @@
tile.handleUpdateState(state, /* arg= */ null)
- Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_on))
+ Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_on))
}
@Test
@@ -100,7 +101,7 @@
tile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
}
@Test
@@ -111,6 +112,14 @@
tile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_flashlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_flashlight_icon_off))
+ }
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index b5ec0a0..b5a64b3 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
@@ -35,9 +35,11 @@
import com.android.systemui.SysuiTestCase;
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.qs.tiles.dialog.InternetDialogManager;
@@ -172,7 +174,7 @@
mTile.mSignalCallback.setIsAirplaneMode(state);
mTestableLooper.processAllMessages();
assertThat(mTile.getState().icon).isEqualTo(
- QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable));
+ createExpectedIcon(R.drawable.ic_qs_no_internet_unavailable));
}
@Test
@@ -194,4 +196,12 @@
verify(mWifiStateWorker, times(1)).setWifiEnabled(eq(true));
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 0a1455f..4be1899 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
@@ -22,7 +22,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -30,9 +29,12 @@
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.isEnabled
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
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.KeyguardStateController
import com.android.systemui.statusbar.policy.LocationController
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,27 +54,17 @@
@SmallTest
class LocationTileTest : SysuiTestCase() {
- @Mock
- private lateinit var mockContext: Context
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var qsHost: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var qsHost: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
private val falsingManager = FalsingManagerFake()
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var locationController: LocationController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var panelInteractor: PanelInteractor
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var locationController: LocationController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var panelInteractor: PanelInteractor
+ @Mock private lateinit var uiEventLogger: QsEventLogger
private lateinit var testableLooper: TestableLooper
private lateinit var tile: LocationTile
@@ -83,20 +75,21 @@
testableLooper = TestableLooper.get(this)
`when`(qsHost.context).thenReturn(mockContext)
- tile = LocationTile(
- qsHost,
- uiEventLogger,
- testableLooper.looper,
- Handler(testableLooper.looper),
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger,
- locationController,
- keyguardStateController,
- panelInteractor,
- )
+ tile =
+ LocationTile(
+ qsHost,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ locationController,
+ keyguardStateController,
+ panelInteractor,
+ )
}
@After
@@ -112,8 +105,7 @@
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_off))
}
@Test
@@ -123,8 +115,7 @@
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_location_icon_on))
}
@Test
@@ -140,4 +131,12 @@
verify(panelInteractor).openPanels()
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index dbdf3a4..afe9713 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
@@ -23,7 +23,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -31,8 +30,11 @@
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.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.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.google.common.truth.Truth.assertThat
@@ -41,8 +43,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -54,36 +56,27 @@
const val MICROPHONE_TOGGLE_DISABLED: Boolean = true
}
- @Mock
- private lateinit var host: QSHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
- @Mock
- private lateinit var statusBarStateController: StatusBarStateController
- @Mock
- private lateinit var activityStarter: ActivityStarter
- @Mock
- private lateinit var qsLogger: QSLogger
- @Mock
- private lateinit var privacyController: IndividualSensorPrivacyController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var uiEventLogger: QsEventLogger
- @Mock
- private lateinit var safetyCenterManager: SafetyCenterManager
+ @Mock private lateinit var host: QSHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var uiEventLogger: QsEventLogger
+ @Mock private lateinit var safetyCenterManager: SafetyCenterManager
private lateinit var testableLooper: TestableLooper
private lateinit var tile: MicrophoneToggleTile
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- tile = MicrophoneToggleTile(
+ tile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -95,7 +88,8 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
}
@After
@@ -110,7 +104,7 @@
tile.handleUpdateState(state, MICROPHONE_TOGGLE_ENABLED)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_on))
}
@Test
@@ -119,13 +113,14 @@
tile.handleUpdateState(state, MICROPHONE_TOGGLE_DISABLED)
- assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_off))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_mic_access_off))
}
@Test
fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- val micTile = MicrophoneToggleTile(
+ val micTile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -137,7 +132,8 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
micTile.destroy()
testableLooper.processAllMessages()
@@ -146,7 +142,8 @@
@Test
fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- val micTile = MicrophoneToggleTile(
+ val micTile =
+ MicrophoneToggleTile(
host,
uiEventLogger,
testableLooper.looper,
@@ -158,9 +155,18 @@
qsLogger,
privacyController,
keyguardStateController,
- safetyCenterManager)
+ safetyCenterManager,
+ )
assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
micTile.destroy()
testableLooper.processAllMessages()
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 848c8db..9173ac9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -94,11 +94,7 @@
private val zenModeRepository = kosmos.zenModeRepository
private val tileDataInteractor =
ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher)
- private val mapper =
- ModesTileMapper(
- context.resources,
- context.theme,
- )
+ private val mapper = ModesTileMapper(context.resources, context.theme)
private lateinit var userActionInteractor: ModesTileUserActionInteractor
private lateinit var secureSettings: SecureSettings
@@ -127,10 +123,7 @@
)
userActionInteractor =
- ModesTileUserActionInteractor(
- inputHandler,
- dialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, dialogDelegate, kosmos.zenModeInteractor)
underTest =
ModesTile(
@@ -185,7 +178,7 @@
ModesTileModel(
isActivated = true,
activeModes = listOf("One", "Two"),
- icon = TestStubDrawable().asIcon()
+ icon = TestStubDrawable().asIcon(),
)
underTest.handleUpdateState(tileState, model)
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 f1c5895..69dab39 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
@@ -23,7 +23,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.dagger.NightDisplayListenerModule
@@ -32,8 +31,11 @@
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.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.LocationController
import com.google.common.truth.Truth
import org.junit.After
@@ -42,8 +44,8 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -72,8 +74,6 @@
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: NightDisplayTile
-
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -97,7 +97,7 @@
mQsLogger,
mLocationController,
mColorDisplayManager,
- mNightDisplayListenerBuilder
+ mNightDisplayListenerBuilder,
)
}
@@ -115,7 +115,7 @@
mTile.handleUpdateState(state, /* arg= */ null)
Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_icon_off))
}
@Test
@@ -125,7 +125,14 @@
mTile.handleUpdateState(state, /* arg= */ null)
- Truth.assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_nightlight_icon_on))
+ Truth.assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_nightlight_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/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index f8f82f2..682daea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
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;
@@ -112,7 +113,7 @@
assertEquals(state.label, mContext.getString(R.string.qr_code_scanner_title));
assertEquals(state.contentDescription, mContext.getString(R.string.qr_code_scanner_title));
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.ic_qr_code_scanner));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.ic_qr_code_scanner));
}
@Test
@@ -133,4 +134,12 @@
assertEquals(state.state, Tile.STATE_INACTIVE);
assertNull(state.secondaryLabel);
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 4068d9f..3395167 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -68,6 +68,7 @@
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;
@@ -294,7 +295,7 @@
public void testHandleUpdateState_updateLabelAndIcon_noIconFromApi() {
when(mQuickAccessWalletClient.getTileIcon()).thenReturn(null);
QSTile.State state = new QSTile.State();
- QSTile.Icon icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_wallet_lockscreen);
+ QSTile.Icon icon = createExpectedIcon(R.drawable.ic_wallet_lockscreen);
mTile.handleUpdateState(state, null);
@@ -574,5 +575,13 @@
CARD_ID, INVALID_CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build();
}
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 1aff45b..2345128 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
@@ -46,6 +46,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.ReduceBrightColorsController;
+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.drawable;
@@ -234,7 +235,7 @@
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_on));
+ assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_on));
}
@Test
@@ -245,7 +246,15 @@
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(drawable.qs_extra_dim_icon_off));
+ assertEquals(state.icon, createExpectedIcon(drawable.qs_extra_dim_icon_off));
+ }
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 4193063..7fb0eab 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
@@ -41,6 +41,7 @@
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;
@@ -247,7 +248,7 @@
mLockTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_off));
}
@Test
@@ -257,7 +258,7 @@
mLockTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_auto_rotate_icon_on));
}
@@ -281,4 +282,12 @@
private void disableCameraBasedRotation() {
when(mRotationPolicyWrapper.isCameraRotationEnabled()).thenReturn(false);
}
+
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 6ebe830..a7c7a78 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
@@ -48,6 +48,7 @@
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.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -268,7 +269,7 @@
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
}
@Test
@@ -279,7 +280,7 @@
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_on));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_on));
}
@Test
@@ -290,7 +291,7 @@
mTile.handleUpdateState(state, /* arg= */ null);
- assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
+ assertEquals(state.icon, createExpectedIcon(R.drawable.qs_screen_record_icon_off));
}
@Test
@@ -316,4 +317,12 @@
.notifyPermissionRequestDisplayed(mContext.getUserId());
}
+ 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/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index 8324a73..773e225 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
@@ -25,7 +25,6 @@
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.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -33,8 +32,11 @@
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.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.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.LocationController
@@ -95,7 +97,7 @@
qsLogger,
configurationController,
batteryController,
- locationController
+ locationController,
)
}
@@ -112,8 +114,7 @@
tile.handleUpdateState(state, /* arg= */ null)
- assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
+ assertThat(state.icon).isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_on))
}
@Test
@@ -124,7 +125,7 @@
tile.handleUpdateState(state, /* arg= */ null)
assertThat(state.icon)
- .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
+ .isEqualTo(createExpectedIcon(R.drawable.qs_light_dark_theme_icon_off))
}
private fun setNightModeOn() {
@@ -136,4 +137,12 @@
`when`(uiModeManager.nightMode).thenReturn(UiModeManager.MODE_NIGHT_NO)
configuration.uiMode = Configuration.UI_MODE_NIGHT_NO
}
+
+ 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/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 52c476e..e4a9888 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.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 @@
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/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index cd58127..88b0046 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.qs.tiles.impl.modes.domain.interactor
import android.graphics.drawable.TestStubDrawable
@@ -21,16 +23,23 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.asIcon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,17 +52,17 @@
@EnableFlags(android.app.Flags.FLAG_MODES_UI)
class ModesTileUserActionInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val inputHandler = kosmos.qsTileIntentUserInputHandler
private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+ private val zenModeRepository = kosmos.zenModeRepository
+ private val zenModeInteractor = kosmos.zenModeInteractor
private val underTest =
- ModesTileUserActionInteractor(
- inputHandler,
- mockDialogDelegate,
- )
+ ModesTileUserActionInteractor(inputHandler, mockDialogDelegate, zenModeInteractor)
@Test
- fun handleClick_active() = runTest {
+ fun handleClick_active_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable)
@@ -63,7 +72,7 @@
}
@Test
- fun handleClick_inactive() = runTest {
+ fun handleClick_inactive_showsDialog() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable)
@@ -73,7 +82,63 @@
}
@Test
- fun handleLongClick_active() = runTest {
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_multipleModesActive_deactivatesAll() =
+ testScope.runTest {
+ val activeModes by collectLastValue(zenModeInteractor.activeModes)
+
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Mode 1").setActive(true).build(),
+ TestModeBuilder().setName("Mode 2").setActive(true).build(),
+ )
+ )
+ assertThat(activeModes?.modeNames?.count()).isEqualTo(3)
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(
+ data = modelOf(true, listOf("DND", "Mode 1", "Mode 2"))
+ )
+ )
+
+ assertThat(activeModes?.isAnyActive()).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndActive_deactivatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_ACTIVE)
+ assertThat(dndMode?.isActive).isTrue()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(true, listOf("DND")))
+ )
+
+ assertThat(dndMode?.isActive).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_QS_UI_REFACTOR_COMPOSE_FRAGMENT)
+ fun handleToggleClick_dndInactive_activatesDnd() =
+ testScope.runTest {
+ val dndMode by collectLastValue(zenModeInteractor.dndMode)
+
+ zenModeRepository.addMode(TestModeBuilder.MANUAL_DND_INACTIVE)
+ assertThat(dndMode?.isActive).isFalse()
+
+ underTest.handleInput(
+ QSTileInputTestKtx.toggleClick(data = modelOf(false, emptyList()))
+ )
+
+ assertThat(dndMode?.isActive).isTrue()
+ }
+
+ @Test
+ fun handleLongClick_active_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND"))))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
@@ -82,7 +147,7 @@
}
@Test
- fun handleLongClick_inactive() = runTest {
+ fun handleLongClick_inactive_opensSettings() = runTest {
underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList())))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
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 954215ee..2edb9c6 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 @@
.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 3ebf9f7..a62d9d5 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.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 @@
}
@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 e96dd16..f06bab7 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.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 @@
}
@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 @@
}
@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 11831ca..8a4ddce 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.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 @@
}
@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 @@
}
@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 @@
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/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
index 1b3f29a..dd1b369 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.core
+import android.internal.statusbar.FakeStatusBarService.Companion.SECONDARY_DISPLAY_ID
import android.internal.statusbar.fakeStatusBarService
import android.platform.test.annotations.EnableFlags
import android.view.WindowInsets
@@ -53,7 +54,7 @@
}
@Test
- fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fun start_defaultDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
initializer.start()
@@ -62,7 +63,7 @@
}
@Test
- fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fun start_defaultDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
initializer.start()
@@ -71,6 +72,32 @@
}
@Test
+ fun start_secondaryDisplay_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.statusBars()
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+ .isTrue()
+ // Default display should be unaffected
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+ }
+
+ @Test
+ fun start_secondaryDisplay_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fakeStatusBarService.transientBarTypesSecondaryDisplay = WindowInsets.Type.navigationBars()
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.forDisplay(SECONDARY_DISPLAY_ID).isTransientShown.value)
+ .isFalse()
+ // Default display should be unaffected
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+ }
+
+ @Test
fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
initializer.start()
@@ -85,6 +112,17 @@
fakeStatusBarService.packageName,
fakeStatusBarService.letterboxDetails,
)
+ verify(commandQueueCallbacks)
+ .onSystemBarAttributesChanged(
+ SECONDARY_DISPLAY_ID,
+ fakeStatusBarService.appearanceSecondaryDisplay,
+ fakeStatusBarService.appearanceRegionsSecondaryDisplay,
+ fakeStatusBarService.navbarColorManagedByImeSecondaryDisplay,
+ fakeStatusBarService.behaviorSecondaryDisplay,
+ fakeStatusBarService.requestedVisibleTypesSecondaryDisplay,
+ fakeStatusBarService.packageNameSecondaryDisplay,
+ fakeStatusBarService.letterboxDetailsSecondaryDisplay,
+ )
}
@Test
@@ -105,6 +143,14 @@
fakeStatusBarService.imeBackDisposition,
fakeStatusBarService.showImeSwitcher,
)
+
+ verify(commandQueueCallbacks)
+ .setImeWindowStatus(
+ SECONDARY_DISPLAY_ID,
+ fakeStatusBarService.imeWindowVisSecondaryDisplay,
+ fakeStatusBarService.imeBackDispositionSecondaryDisplay,
+ fakeStatusBarService.showImeSwitcherSecondaryDisplay,
+ )
}
@Test
@@ -117,6 +163,11 @@
.isEqualTo(fakeStatusBarService.disabledFlags1)
assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
.isEqualTo(fakeStatusBarService.disabledFlags2)
+
+ assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(fakeStatusBarService.disabledFlags1SecondaryDisplay)
+ assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(fakeStatusBarService.disabledFlags2SecondaryDisplay)
}
@Test
@@ -125,5 +176,7 @@
assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+ assertThat(commandQueue.disableFlags1ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
+ assertThat(commandQueue.disableFlags2ForDisplay(SECONDARY_DISPLAY_ID)).isNull()
}
}
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 689fc7c..68798a8 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,83 +24,92 @@
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.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.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.flags.andSceneContainer
+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.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 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.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() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class SensitiveContentCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos =
+ testKosmos().apply {
+ // Override some Kosmos objects with mocks or fakes for easier testability
+ dynamicPrivacyController = mockDynamicPrivacyController
+ sensitiveNotificationProtectionController =
+ mockSensitiveNotificationProtectionController
+ statusBarStateController = fakeStatusBarStateController
+ }
- 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()
+ 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()
+ kosmos.mockSensitiveNotificationProtectionController
+ val sceneInteractor: SceneInteractor = kosmos.sceneInteractor
- val coordinator: SensitiveContentCoordinator =
- DaggerTestSensitiveContentCoordinatorComponent.factory()
- .create(
- dynamicPrivacyController,
- lockscreenUserManager,
- keyguardUpdateMonitor,
- statusBarStateController,
- keyguardStateController,
- mSelectedUserInteractor,
- sensitiveNotificationProtectionController,
- deviceEntryInteractor,
- sceneInteractor,
- kosmos.applicationCoroutineScope,
- )
- .coordinator
+ val coordinator: SensitiveContentCoordinator by lazy { kosmos.sensitiveContentCoordinator }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -143,7 +154,7 @@
fun screenshareSecretFilter_flagDisabled_filterNoAdded() {
coordinator.attach(pipeline)
- verify(pipeline, never()).addFinalizeFilter(any(NotifFilter::class.java))
+ verify(pipeline, never()).addFinalizeFilter(any())
}
@Test
@@ -675,13 +686,13 @@
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)
+ statusBarStateController.state = StatusBarState.KEYGUARD
onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -733,26 +744,3 @@
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 8b4f53a..0000000
--- 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 a5fecb8..8420c49 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.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 @@
@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 @@
)
}
+ 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 @@
systemClock,
executor,
kosmos.accessibilityManagerWrapper,
- kosmos.uiEventLoggerFake,
+ uiEventLoggerFake,
javaAdapter,
kosmos.shadeInteractor,
avalancheController,
@@ -121,6 +151,220 @@
}
@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 @@
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 @@
}
@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 @@
}
@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 @@
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 f76f1ce..7f139bd 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.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.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.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 java.util.Optional;
+import dagger.Lazy;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@@ -105,6 +110,7 @@
@Mock private ActivityStarter mActivityStarter;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private KeyguardInteractor mKeyguardInteractor;
+ @Mock private QSPanelController mQSPanelController;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -150,6 +156,7 @@
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 @@
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/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index 9888574..a2ca12c 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 @@
var listener: StatusBarVisibilityChangeListener? = null
override fun bind(
+ displayId: Int,
view: View,
viewModel: HomeStatusBarViewModel,
systemEventChipAnimateIn: ((View) -> Unit)?,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 4e33a59..3d6882c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -251,6 +251,22 @@
}
@Test
+ fun deactivateAllModes_updatesCorrectModes() =
+ testScope.runTest {
+ zenModeRepository.addModes(
+ listOf(
+ TestModeBuilder.MANUAL_DND_ACTIVE,
+ TestModeBuilder().setName("Inactive").setActive(false).build(),
+ TestModeBuilder().setName("Active").setActive(true).build(),
+ )
+ )
+
+ underTest.deactivateAllModes()
+
+ assertThat(zenModeRepository.getModes().filter { it.isActive }).isEmpty()
+ }
+
+ @Test
fun activeModes_computesMainActiveMode() =
testScope.runTest {
val activeModes by collectLastValue(underTest.activeModes)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index e3cbd66..322da32 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -14,7 +14,6 @@
package com.android.systemui.plugins.qs;
-import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -22,6 +21,7 @@
import android.service.quicksettings.Tile;
import android.text.TextUtils;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
@@ -92,6 +92,7 @@
CharSequence getTileLabel();
+ @NonNull
State getState();
default LogMaker populate(LogMaker logMaker) {
@@ -269,6 +270,7 @@
return sb.append(']');
}
+ @NonNull
public State copy() {
State state = new State();
copyTo(state);
@@ -304,6 +306,7 @@
return rt;
}
+ @androidx.annotation.NonNull
@Override
public State copy() {
AdapterState state = new AdapterState();
@@ -316,6 +319,7 @@
class BooleanState extends AdapterState {
public static final int VERSION = 1;
+ @androidx.annotation.NonNull
@Override
public State copy() {
BooleanState state = new BooleanState();
diff --git a/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
new file mode 100644
index 0000000..0640116
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_down_24dp.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,616 L240,376l56,-56 184,184 184,-184 56,56 -240,240Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
new file mode 100644
index 0000000..65a3eef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_up_24dp.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,432 L296,616l-56,-56 240,-240 240,240 -56,56 -184,-184Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index c1852b1..9ac456c 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 478050b..df7adc0 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 e417da4..658f2c2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1509,6 +1509,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>
@@ -1812,6 +1815,7 @@
<string name="volume_ringer_change">Tap to change ringer mode</string>
<string name="volume_ringer_mode">ringer mode</string>
+ <string name="volume_ringer_drawer_closed_content_description"><xliff:g id="volume ringer status" example="Ring">%1$s</xliff:g>, tap to change ringer mode </string>
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
<string name="volume_ringer_hint_mute">mute</string>
@@ -3949,6 +3953,8 @@
<string name="tutorial_action_key_success_title">Well done!</string>
<!-- Text shown to the user after they complete action key tutorial [CHAR LIMIT=NONE] -->
<string name="tutorial_action_key_success_body">You completed the view all apps gesture</string>
+ <!-- Content description for the animation playing during the tutorial. The user can click the animation to pause and unpause playback. [CHAR LIMIT=NONE] -->
+ <string name="tutorial_animation_content_description">Tutorial animation, click to pause and resume play.</string>
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index 08236b7..ca5424b 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 a46b236..9817322 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.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 @@
* 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 @@
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 5c75a49..f530522 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -24,9 +24,9 @@
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/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 1f21af8..ad12229 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -55,7 +55,6 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
@@ -67,7 +66,6 @@
import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
-import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.res.R;
@@ -87,6 +85,7 @@
*/
public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate,
HearingDeviceItemCallback, BluetoothCallback {
+
private static final String TAG = "HearingDevicesDialogDelegate";
@VisibleForTesting
static final String ACTION_BLUETOOTH_DEVICE_DETAILS =
@@ -96,25 +95,27 @@
@VisibleForTesting
static final Intent LIVE_CAPTION_INTENT = new Intent(
"com.android.settings.action.live_caption");
+
private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final DialogTransitionAnimator mDialogTransitionAnimator;
private final ActivityStarter mActivityStarter;
- private final boolean mShowPairNewDevice;
private final LocalBluetoothManager mLocalBluetoothManager;
private final Handler mMainHandler;
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
- private final HapClientProfile mHapClientProfile;
private final HearingDevicesUiEventLogger mUiEventLogger;
+ private final boolean mShowPairNewDevice;
private final int mLaunchSourceId;
- private HearingDevicesListAdapter mDeviceListAdapter;
- private HearingDevicesPresetsController mPresetsController;
- private Context mApplicationContext;
+
private SystemUIDialog mDialog;
+
private RecyclerView mDeviceList;
private List<DeviceItem> mHearingDeviceItemList;
+ private HearingDevicesListAdapter mDeviceListAdapter;
+
private View mPresetLayout;
private Spinner mPresetSpinner;
+ private HearingDevicesPresetsController mPresetController;
private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
new HearingDevicesPresetsController.PresetCallback() {
@@ -122,20 +123,18 @@
public void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos,
int activePresetIndex) {
mMainHandler.post(
- () -> refreshPresetInfoAdapter(presetInfos, activePresetIndex));
+ () -> refreshPresetUi(presetInfos, activePresetIndex));
}
@Override
public void onPresetCommandFailed(int reason) {
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ mPresetController.refreshPresetInfo();
mMainHandler.post(() -> {
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- showPresetErrorToast(mApplicationContext);
+ showErrorToast(R.string.hearing_devices_presets_error);
});
}
};
+
private final List<DeviceItemFactory> mHearingDeviceItemFactoryList = List.of(
new ActiveHearingDeviceItemFactory(),
new AvailableHearingDeviceItemFactory(),
@@ -159,7 +158,6 @@
@AssistedInject
public HearingDevicesDialogDelegate(
- @Application Context applicationContext,
@Assisted boolean showPairNewDevice,
@Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId,
SystemUIDialog.Factory systemUIDialogFactory,
@@ -169,7 +167,6 @@
@Main Handler handler,
AudioManager audioManager,
HearingDevicesUiEventLogger uiEventLogger) {
- mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
mActivityStarter = activityStarter;
@@ -178,7 +175,6 @@
mMainHandler = handler;
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
- mHapClientProfile = mProfileManager.getHapClientProfile();
mUiEventLogger = uiEventLogger;
mLaunchSourceId = launchSourceId;
}
@@ -229,38 +225,26 @@
@Override
public void onActiveDeviceChanged(@Nullable CachedBluetoothDevice activeDevice,
int bluetoothProfile) {
- CachedBluetoothDevice activeHearingDevice;
- mHearingDeviceItemList = getHearingDevicesList();
- if (mPresetsController != null) {
- activeHearingDevice = getActiveHearingDevice(mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
- } else {
- activeHearingDevice = null;
+ refreshDeviceUi();
+ if (mPresetController != null) {
+ mPresetController.setDevice(getActiveHearingDevice());
+ mMainHandler.post(() -> {
+ mPresetLayout.setVisibility(
+ mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
+ });
}
- mMainHandler.post(() -> {
- mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
- final List<BluetoothHapPresetInfo> presetInfos =
- mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
- });
}
@Override
public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state, int bluetoothProfile) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
public void onAclConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
int state) {
- mHearingDeviceItemList = getHearingDevicesList();
- mMainHandler.post(() -> mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList));
+ refreshDeviceUi();
}
@Override
@@ -306,13 +290,9 @@
if (mLocalBluetoothManager == null) {
return;
}
-
mLocalBluetoothManager.getEventManager().registerCallback(this);
- if (mPresetsController != null) {
- mPresetsController.registerHapCallback();
- if (mHapClientProfile != null && !mHapClientProfile.isProfileReady()) {
- mProfileManager.addServiceListener(mPresetsController);
- }
+ if (mPresetController != null) {
+ mPresetController.registerHapCallback();
}
}
@@ -322,37 +302,25 @@
return;
}
- if (mPresetsController != null) {
- mPresetsController.unregisterHapCallback();
- mProfileManager.removeServiceListener(mPresetsController);
+ if (mPresetController != null) {
+ mPresetController.unregisterHapCallback();
}
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
}
- @VisibleForTesting
- void setHearingDevicesPresetsController(HearingDevicesPresetsController controller) {
- mPresetsController = controller;
- }
-
private void setupDeviceListView(SystemUIDialog dialog) {
mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
- mHearingDeviceItemList = getHearingDevicesList();
+ mHearingDeviceItemList = getHearingDeviceItemList();
mDeviceListAdapter = new HearingDevicesListAdapter(mHearingDeviceItemList, this);
mDeviceList.setAdapter(mDeviceListAdapter);
}
private void setupPresetSpinner(SystemUIDialog dialog) {
- if (mPresetsController == null) {
- mPresetsController = new HearingDevicesPresetsController(mProfileManager,
- mPresetCallback);
- }
- final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
- mHearingDeviceItemList);
- mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
+ mPresetController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+ mPresetController.setDevice(getActiveHearingDevice());
mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
mPresetSpinner.setAdapter(mPresetInfoAdapter);
-
// disable redundant Touch & Hold accessibility action for Switch Access
mPresetSpinner.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
@@ -362,20 +330,18 @@
super.onInitializeAccessibilityNodeInfo(host, info);
}
});
-
- // Refresh the spinner and setSelection(index, false) before setOnItemSelectedListener() to
- // avoid extra onItemSelected() get called when first register the listener.
- final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
+ // Should call setSelection(index, false) for the spinner before setOnItemSelectedListener()
+ // to avoid extra onItemSelected() get called when first register the listener.
+ refreshPresetUi(mPresetController.getAllPresetInfo(),
+ mPresetController.getActivePresetIndex());
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mPresetInfoAdapter.setSelected(position);
mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
mLaunchSourceId);
- mPresetsController.selectPreset(
- mPresetsController.getAllPresetInfo().get(position).getIndex());
+ mPresetController.selectPreset(
+ mPresetController.getAllPresetInfo().get(position).getIndex());
}
@Override
@@ -383,9 +349,8 @@
// Do nothing
}
});
- mPresetLayout.setVisibility(
- (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
- && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+
+ mPresetLayout.setVisibility(mPresetController.isPresetControlAvailable() ? VISIBLE : GONE);
}
private void setupPairNewDeviceButton(SystemUIDialog dialog) {
@@ -405,13 +370,12 @@
}
private void setupRelatedToolsView(SystemUIDialog dialog) {
-
final Context context = dialog.getContext();
final List<ToolItem> toolItemList = new ArrayList<>();
final String[] toolNameArray;
final String[] toolIconArray;
- ToolItem preInstalledItem = getLiveCaption(context);
+ ToolItem preInstalledItem = getLiveCaptionToolItem(context);
if (preInstalledItem != null) {
toolItemList.add(preInstalledItem);
}
@@ -432,7 +396,7 @@
final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
for (int i = 0; i < toolItemList.size(); i++) {
- View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+ View view = createToolView(context, toolItemList.get(i), toolsContainer);
toolsContainer.addView(view);
if (i != toolItemList.size() - 1) {
final int spaceSize = context.getResources().getDimensionPixelSize(
@@ -444,8 +408,14 @@
}
}
- private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos,
- int activePresetIndex) {
+ private void refreshDeviceUi() {
+ mHearingDeviceItemList = getHearingDeviceItemList();
+ mMainHandler.post(() -> {
+ mDeviceListAdapter.refreshDeviceItemList(mHearingDeviceItemList);
+ });
+ }
+
+ private void refreshPresetUi(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) {
mPresetInfoAdapter.clear();
mPresetInfoAdapter.addAll(
presetInfos.stream().map(BluetoothHapPresetInfo::getName).toList());
@@ -460,12 +430,11 @@
}
}
- private List<DeviceItem> getHearingDevicesList() {
+ private List<DeviceItem> getHearingDeviceItemList() {
if (mLocalBluetoothManager == null
|| !mLocalBluetoothManager.getBluetoothAdapter().isEnabled()) {
return emptyList();
}
-
return mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy().stream()
.map(this::createHearingDeviceItem)
.filter(Objects::nonNull)
@@ -473,8 +442,8 @@
}
@Nullable
- private CachedBluetoothDevice getActiveHearingDevice(List<DeviceItem> hearingDeviceItemList) {
- return hearingDeviceItemList.stream()
+ private CachedBluetoothDevice getActiveHearingDevice() {
+ return mHearingDeviceItemList.stream()
.filter(item -> item.getType() == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
.map(DeviceItem::getCachedBluetoothDevice)
.findFirst()
@@ -495,7 +464,7 @@
}
@NonNull
- private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+ private View createToolView(Context context, ToolItem item, ViewGroup container) {
View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
false);
ImageView icon = view.requireViewById(R.id.tool_icon);
@@ -522,7 +491,7 @@
return view;
}
- private ToolItem getLiveCaption(Context context) {
+ private ToolItem getLiveCaptionToolItem(Context context) {
final PackageManager packageManager = context.getPackageManager();
LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName());
final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT,
@@ -534,7 +503,6 @@
LIVE_CAPTION_INTENT,
/* isCustomIcon= */ true);
}
-
return null;
}
@@ -544,7 +512,7 @@
}
}
- private void showPresetErrorToast(Context context) {
- Toast.makeText(context, R.string.hearing_devices_presets_error, Toast.LENGTH_SHORT).show();
+ private void showErrorToast(int stringResId) {
+ Toast.makeText(mDialog.getContext(), stringResId, Toast.LENGTH_SHORT).show();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
index aa95fd0..e109108 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesPresetsController.java
@@ -25,16 +25,18 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.utils.ThreadUtils;
+import java.util.ArrayList;
import java.util.List;
/**
- * The controller of the hearing devices presets of the bluetooth Hearing Access Profile.
+ * The controller of handling hearing device preset with Bluetooth Hearing Access Profile(HAP).
*/
public class HearingDevicesPresetsController implements
LocalBluetoothProfileManager.ServiceListener, BluetoothHapClient.Callback {
@@ -46,11 +48,13 @@
private final HapClientProfile mHapClientProfile;
private final PresetCallback mPresetCallback;
- private CachedBluetoothDevice mActiveHearingDevice;
+ private CachedBluetoothDevice mDevice;
+ private List<BluetoothHapPresetInfo> mPresetInfos = new ArrayList<>();
+ private int mActivePresetIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
private int mSelectedPresetIndex;
- public HearingDevicesPresetsController(LocalBluetoothProfileManager profileManager,
- PresetCallback presetCallback) {
+ public HearingDevicesPresetsController(@NonNull LocalBluetoothProfileManager profileManager,
+ @Nullable PresetCallback presetCallback) {
mProfileManager = profileManager;
mHapClientProfile = mProfileManager.getHapClientProfile();
mPresetCallback = presetCallback;
@@ -61,7 +65,7 @@
if (mHapClientProfile != null && mHapClientProfile.isProfileReady()) {
mProfileManager.removeServiceListener(this);
registerHapCallback();
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@@ -72,51 +76,53 @@
@Override
public void onPresetSelected(@NonNull BluetoothDevice device, int presetIndex, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetSelected, device: " + device.getAddress()
+ ", presetIndex: " + presetIndex + ", reason: " + reason);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetInfoChanged(@NonNull BluetoothDevice device,
@NonNull List<BluetoothHapPresetInfo> presetInfoList, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
if (DEBUG) {
Log.d(TAG, "onPresetInfoChanged, device: " + device.getAddress()
+ ", reason: " + reason + ", infoList: " + presetInfoList);
}
- mPresetCallback.onPresetInfoUpdated(getAllPresetInfo(), getActivePresetIndex());
+ refreshPresetInfo();
}
}
@Override
public void onPresetSelectionFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onPresetSelectionForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onPresetSelectionForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
selectPresetIndependently(mSelectedPresetIndex);
@@ -125,33 +131,43 @@
@Override
public void onSetPresetNameFailed(@NonNull BluetoothDevice device, int reason) {
- if (mActiveHearingDevice == null) {
+ if (mDevice == null) {
return;
}
- if (device.equals(mActiveHearingDevice.getDevice())) {
+ if (device.equals(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameFailed, device: " + device.getAddress()
+ ", reason: " + reason);
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
}
@Override
public void onSetPresetNameForGroupFailed(int hapGroupId, int reason) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
- if (hapGroupId == mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice())) {
+ if (hapGroupId == mHapClientProfile.getHapGroup(mDevice.getDevice())) {
Log.w(TAG, "onSetPresetNameForGroupFailed, group: " + hapGroupId
+ ", reason: " + reason);
}
- mPresetCallback.onPresetCommandFailed(reason);
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetCommandFailed(reason);
+ }
}
/**
- * Registers a callback to be notified about operation changed for {@link HapClientProfile}.
+ * Registers a callback to be notified about operation changed of {@link HapClientProfile}.
*/
public void registerHapCallback() {
if (mHapClientProfile != null) {
+ if (!mHapClientProfile.isProfileReady()) {
+ mProfileManager.addServiceListener(this);
+ Log.w(TAG, "Profile is not ready yet, the callback will be registered once the "
+ + "profile is ready.");
+ return;
+ }
try {
mHapClientProfile.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
} catch (IllegalArgumentException e) {
@@ -163,9 +179,10 @@
}
/**
- * Removes a previously-added {@link HapClientProfile} callback.
+ * Removes a previously-added {@link HapClientProfile} callback if exist.
*/
public void unregisterHapCallback() {
+ mProfileManager.removeServiceListener(this);
if (mHapClientProfile != null) {
try {
mHapClientProfile.unregisterCallback(this);
@@ -177,39 +194,94 @@
}
/**
- * Sets the hearing device for this controller to control the preset if it supports
- * {@link HapClientProfile}.
+ * Sets the device for this controller to control the preset if it supports
+ * {@link HapClientProfile}, otherwise the device of this controller will be {@code null}.
*
- * @param activeHearingDevice the {@link CachedBluetoothDevice} need to be hearing aid device
- * and support {@link HapClientProfile}.
+ * @param device the {@link CachedBluetoothDevice} set to the controller
*/
- public void setHearingDeviceIfSupportHap(CachedBluetoothDevice activeHearingDevice) {
- if (mHapClientProfile == null || activeHearingDevice == null) {
- mActiveHearingDevice = null;
- return;
- }
- if (activeHearingDevice.getProfiles().stream().anyMatch(
+ public void setDevice(@Nullable CachedBluetoothDevice device) {
+ if (device != null && device.getProfiles().stream().anyMatch(
profile -> profile instanceof HapClientProfile)) {
- mActiveHearingDevice = activeHearingDevice;
+ mDevice = device;
} else {
- mActiveHearingDevice = null;
+ mDevice = null;
+ }
+ refreshPresetInfo();
+ }
+
+ /**
+ * Refreshes the preset info of {@code mDevice}. If the preset info list or the active preset
+ * index is updated, the {@link PresetCallback#onPresetInfoUpdated(List, int)} will be called
+ * to notify the change.
+ *
+ * <b>Note:</b> If {@code mDevice} is null, the cached preset info and active preset index will
+ * be reset to empty list and {@code BluetoothHapClient.PRESET_INDEX_UNAVAILABLE} respectively.
+ */
+ public void refreshPresetInfo() {
+ List<BluetoothHapPresetInfo> updatedInfos = new ArrayList<>();
+ int updatedActiveIndex = BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ if (mHapClientProfile != null && mDevice != null) {
+ updatedInfos = mHapClientProfile.getAllPresetInfo(mDevice.getDevice()).stream().filter(
+ BluetoothHapPresetInfo::isAvailable).toList();
+ updatedActiveIndex = mHapClientProfile.getActivePresetIndex(mDevice.getDevice());
+ }
+ final boolean infoUpdated = !mPresetInfos.equals(updatedInfos);
+ final boolean activeIndexUpdated = mActivePresetIndex != updatedActiveIndex;
+ mPresetInfos = updatedInfos;
+ mActivePresetIndex = updatedActiveIndex;
+ if (infoUpdated || activeIndexUpdated) {
+ if (mPresetCallback != null) {
+ mPresetCallback.onPresetInfoUpdated(mPresetInfos, mActivePresetIndex);
+ }
}
}
/**
- * Selects the currently active preset for {@code mActiveHearingDevice} individual device or
- * the device group according to whether it supports synchronized presets or not.
+ * @return if the preset control is available. The preset control is available only
+ * when the {@code mDevice} supports HAP and the retrieved preset info list is not empty.
+ */
+ public boolean isPresetControlAvailable() {
+ boolean deviceValid = mDevice != null && mDevice.isConnectedHapClientDevice();
+ boolean hasPreset = mPresetInfos != null && !mPresetInfos.isEmpty();
+ return deviceValid && hasPreset;
+ }
+
+ /**
+ * @return a list of {@link BluetoothHapPresetInfo} retrieved from {@code mDevice}
+ */
+ public List<BluetoothHapPresetInfo> getAllPresetInfo() {
+ if (mDevice == null || mHapClientProfile == null) {
+ return emptyList();
+ }
+ return mPresetInfos;
+ }
+
+ /**
+ * Gets the currently active preset of {@code mDevice}.
+ *
+ * @return active preset index
+ */
+ public int getActivePresetIndex() {
+ if (mDevice == null || mHapClientProfile == null) {
+ return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+ }
+ return mActivePresetIndex;
+ }
+
+ /**
+ * Selects the preset for {@code mDevice}. Performs individual or group operation according
+ * to whether the device supports synchronized presets feature or not.
*
* @param presetIndex an index of one of the available presets
*/
public void selectPreset(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
mSelectedPresetIndex = presetIndex;
boolean supportSynchronizedPresets = mHapClientProfile.supportsSynchronizedPresets(
- mActiveHearingDevice.getDevice());
- int hapGroupId = mHapClientProfile.getHapGroup(mActiveHearingDevice.getDevice());
+ mDevice.getDevice());
+ int hapGroupId = mHapClientProfile.getHapGroup(mDevice.getDevice());
if (supportSynchronizedPresets) {
if (hapGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
selectPresetSynchronously(hapGroupId, presetIndex);
@@ -222,63 +294,37 @@
}
}
- /**
- * Gets all preset info for {@code mActiveHearingDevice} device.
- *
- * @return a list of all known preset info
- */
- public List<BluetoothHapPresetInfo> getAllPresetInfo() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
- return emptyList();
- }
- return mHapClientProfile.getAllPresetInfo(mActiveHearingDevice.getDevice()).stream().filter(
- BluetoothHapPresetInfo::isAvailable).toList();
- }
-
- /**
- * Gets the currently active preset for {@code mActiveHearingDevice} device.
- *
- * @return active preset index
- */
- public int getActivePresetIndex() {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
- return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
- }
- return mHapClientProfile.getActivePresetIndex(mActiveHearingDevice.getDevice());
- }
-
private void selectPresetSynchronously(int groupId, int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetSynchronously"
+ ", presetIndex: " + presetIndex
+ ", groupId: " + groupId
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
mHapClientProfile.selectPresetForGroup(groupId, presetIndex);
}
private void selectPresetIndependently(int presetIndex) {
- if (mActiveHearingDevice == null || mHapClientProfile == null) {
+ if (mDevice == null || mHapClientProfile == null) {
return;
}
if (DEBUG) {
Log.d(TAG, "selectPresetIndependently"
+ ", presetIndex: " + presetIndex
- + ", device: " + mActiveHearingDevice.getAddress());
+ + ", device: " + mDevice.getAddress());
}
- mHapClientProfile.selectPreset(mActiveHearingDevice.getDevice(), presetIndex);
- final CachedBluetoothDevice subDevice = mActiveHearingDevice.getSubDevice();
+ mHapClientProfile.selectPreset(mDevice.getDevice(), presetIndex);
+ final CachedBluetoothDevice subDevice = mDevice.getSubDevice();
if (subDevice != null) {
if (DEBUG) {
Log.d(TAG, "selectPreset for subDevice, device: " + subDevice);
}
mHapClientProfile.selectPreset(subDevice.getDevice(), presetIndex);
}
- for (final CachedBluetoothDevice memberDevice :
- mActiveHearingDevice.getMemberDevice()) {
+ for (final CachedBluetoothDevice memberDevice : mDevice.getMemberDevice()) {
if (DEBUG) {
Log.d(TAG, "selectPreset for memberDevice, device: " + memberDevice);
}
@@ -294,9 +340,8 @@
/**
* Called when preset info from {@link HapClientProfile} operation get updated.
*
- * @param presetInfos all preset info for {@code mActiveHearingDevice} device
- * @param activePresetIndex currently active preset index for {@code mActiveHearingDevice}
- * device
+ * @param presetInfos all preset info of {@code mDevice}
+ * @param activePresetIndex currently active preset index of {@code mDevice}
*/
void onPresetInfoUpdated(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex);
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 d4e74d3..9e1b09c 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 @@
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 @@
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 cbdb882..5cf4b4f 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 @@
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 @@
* 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/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 9763295..8a5e011 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -29,6 +29,7 @@
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -39,7 +40,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlin.math.max
/** Encapsulates business logic for interacting with the UDFPS overlay. */
@SysUISingleton
@@ -55,10 +55,7 @@
private fun calculateIconSize(): Int {
val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
if (pixelPitch <= 0) {
- Log.e(
- "UdfpsOverlayInteractor",
- "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
- )
+ Log.e(TAG, "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.")
}
return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
}
@@ -84,13 +81,15 @@
}
/** Sets whether Udfps overlay should handle touches */
- fun setHandleTouches(shouldHandle: Boolean = true) {
- if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
+ fun setHandleTouches(shouldHandle: Boolean) {
+ if (authController.isUdfpsSupported) {
fingerprintManager?.setIgnoreDisplayTouches(
requestId.value,
authController.udfpsProps!!.get(0).sensorId,
!shouldHandle,
)
+ } else {
+ Log.d(TAG, "setIgnoreDisplayTouches not set, UDFPS not supported")
}
_shouldHandleTouches.value = shouldHandle
}
@@ -123,12 +122,14 @@
// Padding between the fingerprint icon and its bounding box in pixels.
val iconPadding: Flow<Int> =
- udfpsOverlayParams.map { params ->
- val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
- val nativePadding = (sensorWidth - iconSize) / 2
- // padding can be negative when udfpsOverlayParams has not been initialized yet.
- max(0, (nativePadding * params.scaleFactor).toInt())
- }.distinctUntilChanged()
+ udfpsOverlayParams
+ .map { params ->
+ val sensorWidth = params.nativeSensorBounds.right - params.nativeSensorBounds.left
+ val nativePadding = (sensorWidth - iconSize) / 2
+ // padding can be negative when udfpsOverlayParams has not been initialized yet.
+ max(0, (nativePadding * params.scaleFactor).toInt())
+ }
+ .distinctUntilChanged()
companion object {
private const val TAG = "UdfpsOverlayInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index b2d02ed..a31e61f 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 @@
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 074b64e..69f4f6d 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 @@
/** 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 d648b9c..5644e6b 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.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 @@
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/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 0cdbad4..862b05b 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 @@
.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 @@
.stateIn(
scope = bgScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = null
+ initialValue = null,
)
}
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 9cd6465..eb7420f 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 @@
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 @@
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/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index e78ce3b..f804c2e 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.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 e76fd47..c425bee 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.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 @@
@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 @@
}
if (
- Flags.communalHubOnMobile() &&
+ communalSettingsInteractor.isV2FlagEnabled() &&
packageManager.getComponentEnabledSetting(overlayServiceComponent) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
) {
@@ -111,7 +112,7 @@
}
// 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 aee3a45..571b37f 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 @@
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.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 @@
private final SceneInteractor mSceneInteractor;
private final CommunalInteractor mCommunalInteractor;
+ private final CommunalSettingsInteractor mCommunalSettingsInteractor;
private boolean mCommunalAvailable;
@@ -383,6 +384,7 @@
KeyguardUpdateMonitor keyguardUpdateMonitor,
ScrimManager scrimManager,
CommunalInteractor communalInteractor,
+ CommunalSettingsInteractor communalSettingsInteractor,
SceneInteractor sceneInteractor,
SystemDialogsCloser systemDialogsCloser,
UiEventLogger uiEventLogger,
@@ -411,6 +413,7 @@
mDreamOverlayCallbackController = dreamOverlayCallbackController;
mWindowTitle = windowTitle;
mCommunalInteractor = communalInteractor;
+ mCommunalSettingsInteractor = communalSettingsInteractor;
mSceneInteractor = sceneInteractor;
mSystemDialogsCloser = systemDialogsCloser;
mGestureInteractor = gestureInteractor;
@@ -488,7 +491,7 @@
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 @@
} 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 7242770..e264635 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.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.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.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 @@
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 @@
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 @@
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 @@
}
}
+ 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/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index abd39cc..ad18817 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -36,6 +36,9 @@
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.node.Ref
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.util.lerp
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.compose.LottieAnimation
@@ -47,6 +50,7 @@
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
@Composable
fun TutorialAnimation(
@@ -104,11 +108,15 @@
isPlaying = isPlaying,
restartOnPlay = false,
)
+ val animationDescription = stringResource(R.string.tutorial_animation_content_description)
LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = animationProperties,
- modifier = Modifier.fillMaxSize().clickable { isPlaying = !isPlaying },
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable { isPlaying = !isPlaying }
+ .semantics { contentDescription = animationDescription },
)
}
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 8c393e2..3020e5d 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.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 @@
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 4af3786..8afec04 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.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 @@
@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 @@
if (inputDevice == null) {
emptyList()
} else {
- val sources = toInternalGroupSources(inputGestures)
+ val sources = inputGestureDataAdapter.toInternalGroupSources(inputGestures)
val supportedKeyCodes =
shortcutCategoriesUtils.fetchSupportedKeyCodes(
inputDevice.id,
@@ -216,7 +210,8 @@
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
@KeyGestureType
@@ -232,7 +227,8 @@
return null
}
- return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureDataAdapter
+ .getKeyGestureTypeFromShortcutLabel(shortcutBeingCustomized.label)
}
private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
@@ -261,70 +257,6 @@
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 0000000..df7101e
--- /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 1c380c2..30a2f33 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_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 @@
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 @@
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 @@
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 7d9e010..2724918 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.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.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 @@
onKeyboardSettingsClicked,
shortcutsUiState.isShortcutCustomizerFlagEnabled,
onCustomizationRequested,
- shortcutsUiState.shouldShowResetButton
+ shortcutsUiState.shouldShowResetButton,
)
}
}
@@ -380,7 +380,7 @@
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 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 @@
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/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index d1f9fa2..e8d3bfa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -27,7 +27,6 @@
import android.service.notification.ZenModeConfig
import android.util.Log
import com.android.settingslib.notification.modes.EnableZenModeDialog
-import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModeDialogMetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -99,15 +98,6 @@
private var oldIsAvailable = false
private var settingsValue: Int = 0
- private val dndMode: StateFlow<ZenMode?> by lazy {
- ModesUi.assertInNewMode()
- interactor.dndMode.stateIn(
- scope = backgroundScope,
- started = SharingStarted.Eagerly,
- initialValue = null,
- )
- }
-
private val isAvailable: StateFlow<Boolean> by lazy {
ModesUi.assertInNewMode()
interactor.isZenAvailable.stateIn(
@@ -146,7 +136,7 @@
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
if (ModesUi.isEnabled) {
- combine(isAvailable, dndMode) { isAvailable, dndMode ->
+ combine(isAvailable, interactor.dndMode) { isAvailable, dndMode ->
if (!isAvailable) {
KeyguardQuickAffordanceConfig.LockScreenState.Hidden
} else if (dndMode?.isActive == true) {
@@ -222,7 +212,7 @@
if (!isAvailable.value) {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
} else {
- val dnd = dndMode.value
+ val dnd = interactor.dndMode.value
if (dnd == null) {
Log.wtf(TAG, "Triggered DND but it's null!?")
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
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 71f29c0..d335a18 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.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 @@
@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 @@
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 @@
}
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 deef2a6..a999211 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.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.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.filter
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -178,7 +176,8 @@
newScene = CommunalScenes.Communal,
loggingReason = "FromDreamingTransitionInteractor",
transitionKey =
- if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+ if (communalSettingsInteractor.isV2FlagEnabled())
+ CommunalTransitionKeys.SimpleFade
else null,
)
} else {
@@ -226,8 +225,15 @@
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 7cd2744..f078fe2 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 @@
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 @@
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 9df293b..3b99bb4 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 @@
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/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ba3357c..7c7f48e0 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.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 @@
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 0d464f5..dc3b582 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 @@
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 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 @@
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 9dc21fb..8d9f49e 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.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 @@
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 @@
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 02498d6..3c72520 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.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 @@
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 @@
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 @@
),
)
+ 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 @@
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 8ef6375..cc87206 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.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.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.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 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 @@
false /* if the dismiss should be deferred */
},
null /* cancelAction */,
- true /* afterKeyguardGone */
+ true, /* afterKeyguardGone */
)
}
@@ -269,29 +269,7 @@
.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 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 @@
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 @@
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 @@
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/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 1f55ac7..f4bf53ca 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.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 @@
): 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 47c4ffd..f17abe8 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 4af1b22..e493cbe 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 @@
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 b6dbf4d..39408d3 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.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 c6141a1..a05747d 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.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.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 @@
@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 @@
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/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index c798e5b..13b3311 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -126,7 +126,7 @@
modifier: Modifier = Modifier,
detailsViewModel: DetailsViewModel?,
) {
- val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
+ val state: QSTile.State by tile.state.collectAsStateWithLifecycle(tile.currentState)
val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
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
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
index c2764f9..85db952 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.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 0000000..37fa9e7
--- /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 4a18872..3fcb2ab 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.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/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
index 44dd801..9462321 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt
@@ -24,6 +24,7 @@
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onStart
@Immutable
@@ -37,6 +38,7 @@
awaitClose { tile.removeCallback(callback) }
}
.onStart { emit(tile.state) }
+ .filterNotNull()
.distinctUntilChanged()
val currentState: QSTile.State
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
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
index b033473..f606218 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 0000000..0fde855
--- /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/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 464eeda..1d1e991 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -369,6 +369,7 @@
mHandler.sendEmptyMessage(H.INITIALIZE);
}
+ @androidx.annotation.NonNull
public TState getState() {
return mState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 9c63456..0051bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -109,6 +109,11 @@
userActionInteractor.handleClick(expandable)
}
+ override fun handleSecondaryClick(expandable: Expandable?) = runBlocking {
+ val model = dataInteractor.getCurrentTileModel()
+ userActionInteractor.handleToggleClick(model)
+ }
+
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
@VisibleForTesting
@@ -125,6 +130,7 @@
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
+ handlesSecondaryClick = true
}
}
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 17b78eb..e8c4274 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 @@
* 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 aeb6cef..224fa10 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 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 @@
.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 244f024..bed8021 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
@@ -149,7 +149,7 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
static final int MAX_WIFI_ENTRY_COUNT = 3;
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 a963b28..fdb15b9 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,15 +18,23 @@
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
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.connectivity.AccessPointController
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.withContext
@@ -61,11 +69,18 @@
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/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
index eb8b23c..594394f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -18,13 +18,16 @@
import android.content.Intent
import android.provider.Settings
+import android.util.Log
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.QSComposeFragment
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.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
import javax.inject.Inject
@@ -35,16 +38,19 @@
private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
// TODO(b/353896370): The domain layer should not have to depend on the UI layer.
private val dialogDelegate: ModesDialogDelegate,
+ private val zenModeInteractor: ZenModeInteractor,
) : QSTileUserActionInteractor<ModesTileModel> {
val longClickIntent = Intent(Settings.ACTION_ZEN_MODE_SETTINGS)
override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
with(input) {
when (action) {
- is QSTileUserAction.Click,
- is QSTileUserAction.ToggleClick -> {
+ is QSTileUserAction.Click -> {
handleClick(action.expandable)
}
+ is QSTileUserAction.ToggleClick -> {
+ handleToggleClick(input.data)
+ }
is QSTileUserAction.LongClick -> {
qsTileIntentUserInputHandler.handle(action.expandable, longClickIntent)
}
@@ -56,4 +62,29 @@
// Show a dialog with the list of modes to configure.
dialogDelegate.showDialog(expandable)
}
+
+ fun handleToggleClick(modesTileModel: ModesTileModel) {
+ if (QSComposeFragment.isUnexpectedlyInLegacyMode()) {
+ return
+ }
+
+ // If no modes are on, turn on DND since it's the highest-priority mode. Otherwise, turn
+ // them all off.
+ // We want this toggle to work as a shortcut to DND in most cases, but it should still
+ // correctly toggle the tile state to "off" as the user would expect when more modes are on.
+ if (modesTileModel.activeModes.isEmpty()) {
+ val dnd = zenModeInteractor.dndMode.value
+ if (dnd == null) {
+ Log.wtf(TAG, "Triggered DND but it's null!?")
+ return
+ }
+ zenModeInteractor.activateMode(dnd)
+ } else {
+ zenModeInteractor.deactivateAllModes()
+ }
+ }
+
+ companion object {
+ const val TAG = "ModesTileUserActionInteractor"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index bac048f..1507ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -45,7 +45,11 @@
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(
+ QSTileState.UserAction.CLICK,
+ QSTileState.UserAction.LONG_CLICK,
+ QSTileState.UserAction.TOGGLE_CLICK,
+ )
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
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 b1b0001..e8b9926 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 @@
/** 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 f9a1ad5..632eeef 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.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 @@
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"),
@@ -207,8 +212,9 @@
qsTileViewModel.destroy()
}
- override fun getState(): QSTile.State? =
+ override fun getState(): QSTile.State =
qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) }
+ ?: QSTile.State()
override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId
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 62b1203..91d9079 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.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 @@
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/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
index c5c705c..4ad222d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt
@@ -162,7 +162,12 @@
)
if (cutout == null) {
- screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom)
+ screenshotStatic.setPadding(
+ navBarInsets.left,
+ navBarInsets.top,
+ navBarInsets.right,
+ navBarInsets.bottom,
+ )
} else {
val waterfall = cutout.waterfallInsets
if (inPortrait) {
@@ -179,9 +184,9 @@
)
} else {
screenshotStatic.setPadding(
- max(cutout.safeInsetLeft, waterfall.left),
+ max(cutout.safeInsetLeft, waterfall.left, navBarInsets.left),
waterfall.top,
- max(cutout.safeInsetRight, waterfall.right),
+ max(cutout.safeInsetRight, waterfall.right, navBarInsets.right),
max(
navBarInsets.bottom + verticalPadding,
waterfall.bottom + verticalPadding,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 31780a5..61ac1a02 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.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 @@
@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 @@
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 6f491e7..85b50d3 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.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 @@
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 c57c807..571a3e4 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.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 @@
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 bc4241d..4588b19 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 @@
/** 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 b2f7e2f..2cd5bb3 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.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 @@
/** 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 730784a..cf69d40 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.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 @@
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 @@
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,40 +111,64 @@
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
}
-
- // 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),
- )
+ setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
}
}
}
+ 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)
+ }
+
+ 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? {
return this.findViewById(CUSTOM_ICON_VIEW_ID)
}
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 cf07af1..2dce4e3 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.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 @@
"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/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index c37b01f..a9c2784 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
) : ConnectivityState() {
@JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
- TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false)
@JvmField var serviceState: ServiceState? = null
@JvmField var signalStrength: SignalStrength? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index 614f0f4..d24edda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.os.Binder
import android.os.RemoteException
+import android.view.Display
import android.view.WindowInsets
import com.android.internal.statusbar.IStatusBarService
import com.android.internal.statusbar.RegisterStatusBarResult
@@ -47,20 +48,32 @@
override fun start() {
StatusBarConnectedDisplays.assertInNewMode()
- val result: RegisterStatusBarResult =
+ val resultPerDisplay: Map<String, RegisterStatusBarResult> =
try {
- barService.registerStatusBar(commandQueue)
+ barService.registerStatusBarForAllDisplays(commandQueue)
} catch (ex: RemoteException) {
ex.rethrowFromSystemServer()
return
}
- createNavigationBar(result)
-
- if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
- statusBarModeRepository.defaultDisplay.showTransient()
+ resultPerDisplay[Display.DEFAULT_DISPLAY.toString()]?.let {
+ createNavigationBar(it)
+ // Set up the initial icon state
+ val numIcons: Int = it.mIcons.size
+ for (i in 0 until numIcons) {
+ commandQueue.setIcon(it.mIcons.keyAt(i), it.mIcons.valueAt(i))
+ }
}
- val displayId = context.display.displayId
+
+ for ((displayId, result) in resultPerDisplay.entries) {
+ initializeStatusBarForDisplay(displayId.toInt(), result)
+ }
+ }
+
+ private fun initializeStatusBarForDisplay(displayId: Int, result: RegisterStatusBarResult) {
+ if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
+ statusBarModeRepository.forDisplay(displayId).showTransient()
+ }
val commandQueueCallbacks = commandQueueCallbacksLazy.get()
commandQueueCallbacks.onSystemBarAttributesChanged(
displayId,
@@ -81,12 +94,6 @@
result.mShowImeSwitcher,
)
- // Set up the initial icon state
- val numIcons: Int = result.mIcons.size
- for (i in 0 until numIcons) {
- commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
- }
-
// set the initial view visibility
val disabledFlags1 = result.mDisabledFlags1
val disabledFlags2 = result.mDisabledFlags2
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 98ce163..b56a838 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 @@
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 c8e18a8..99edf65 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 @@
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/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 969ff1b..44075af 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.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 @@
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 d1338ea..f2ef2f0 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 @@
// 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 7f95fb0..adfcb71 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.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 @@
@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/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 1cca3ae..d7cc65d 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 @@
// 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 e4768e8..724ba8c 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 @@
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 2166304..c57cede 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 @@
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 1f7bd14..4b71c02 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 @@
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 c2c91b2..1a5dcc1 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 @@
val startTimeMs: Long,
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
+ val notificationKey: String,
) : OngoingCallModel
}
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 72df027..d9b2bd1d 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.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.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 @@
* to support the chip animations.
*/
fun bind(
+ displayId: Int,
view: View,
viewModel: HomeStatusBarViewModel,
systemEventChipAnimateIn: ((View) -> Unit)?,
@@ -65,8 +69,13 @@
}
@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 @@
) {
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 @@
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 @@
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 812e0eb..5614d82 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 @@
// This binder handles everything else
scope.launch {
statusBarViewBinder.bind(
+ context.displayId,
phoneStatusBarView,
statusBarViewModel,
eventAnimationInteractor::animateStatusBarContentForChipEnter,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 9839f9d..12ed647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -40,12 +40,16 @@
import java.time.Duration
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* An interactor that performs business logic related to the status and configuration of Zen Mode
@@ -58,6 +62,7 @@
private val zenModeRepository: ZenModeRepository,
private val notificationSettingsRepository: NotificationSettingsRepository,
@Background private val bgDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val iconLoader: ZenIconLoader,
deviceProvisioningRepository: DeviceProvisioningRepository,
userSetupRepository: UserSetupRepository,
@@ -101,13 +106,16 @@
/**
* Returns the special "manual DND" mode.
*
- * This is only meant as a temporary solution for "legacy" UI pieces that handle DND
- * specifically; any new or migrated features should use modes more generally, through [modes]
- * or [activeModes].
+ * This should only be used when there is a strong reason to handle DND specifically (such as
+ * legacy UI pieces that haven't been updated to use modes more generally, or if the user
+ * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
+ * other scenarios.
*/
- val dndMode: Flow<ZenMode?> by lazy {
+ val dndMode: StateFlow<ZenMode?> by lazy {
ModesUi.assertInNewMode()
- zenModeRepository.modes.map { modes -> modes.singleOrNull { it.isManualDnd } }
+ zenModeRepository.modes
+ .map { modes -> modes.singleOrNull { it.isManualDnd } }
+ .stateIn(scope = backgroundScope, started = SharingStarted.Eagerly, initialValue = null)
}
/** Flow returning the currently active mode(s), if any. */
@@ -201,6 +209,14 @@
zenModeRepository.deactivateMode(zenMode)
}
+ fun deactivateAllModes() {
+ for (mode in zenModeRepository.getModes()) {
+ if (mode.isActive) {
+ deactivateMode(mode)
+ }
+ }
+ }
+
private val zenDuration
get() = notificationSettingsRepository.zenDuration.value
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 b83613b..4071918 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.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 @@
}
},
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 3c24e02..84a8280 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 @@
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 f98ad45..9eee91b 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.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.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 @@
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 @@
selectedButtonUiModel,
unselectedButtonUiModel,
)
+ ringerDrawerTransitionListener.setProgressChangeEnabled(true)
drawerContainer.closeDrawer(uiModel.currentButtonIndex)
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background
- )
}
is RingerDrawerState.Closed -> {
@@ -103,11 +124,31 @@
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 @@
unselectedButtonUiModel,
)
// Open drawer
+ if (
+ uiModel.currentButtonIndex == uiModel.availableButtons.size - 1
+ ) {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(false)
+ } else {
+ ringerDrawerTransitionListener.setProgressChangeEnabled(true)
+ }
drawerContainer.transitionToState(
R.id.volume_dialog_ringer_drawer_open
)
- if (
- uiModel.currentButtonIndex != uiModel.availableButtons.size - 1
- ) {
- volumeDialogBackgroundView.setBackgroundResource(
- R.drawable.volume_dialog_background_small_radius
- )
- }
+ volumeDialogBackgroundView.background =
+ volumeDialogBackgroundView.background.mutate()
}
}
}
@@ -150,6 +193,7 @@
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 @@
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)
}
@@ -196,15 +256,17 @@
uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
ringerButton?.let {
val view = getChildAt(count - index - 1)
+ val isOpen = uiModel.drawerState is RingerDrawerState.Open
if (index == uiModel.currentButtonIndex) {
view.bindDrawerButton(
- uiModel.selectedButton,
+ if (isOpen) it else uiModel.selectedButton,
viewModel,
+ isOpen,
isSelected = true,
isAnimated = isAnimated,
)
} else {
- view.bindDrawerButton(it, viewModel, isAnimated)
+ view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
}
}
}
@@ -214,12 +276,22 @@
private fun View.bindDrawerButton(
buttonViewModel: RingerButtonViewModel,
viewModel: VolumeDialogRingerDrawerViewModel,
+ isOpen: Boolean,
isSelected: Boolean = false,
isAnimated: Boolean = false,
) {
+ val ringerContentDesc = context.getString(buttonViewModel.contentDescriptionResId)
with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
setImageResource(buttonViewModel.imageResId)
- contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+ contentDescription =
+ if (isSelected && !isOpen) {
+ context.getString(
+ R.string.volume_ringer_drawer_closed_content_description,
+ ringerContentDesc,
+ )
+ } else {
+ ringerContentDesc
+ }
if (isSelected && !isAnimated) {
setBackgroundResource(R.drawable.volume_drawer_selection_bg)
setColorFilter(
@@ -354,6 +426,7 @@
private suspend fun ImageButton.animateTo(
ringerButtonUiModel: RingerButtonUiModel,
+ onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> },
roundnessAnimationEndListener: DynamicAnimation.OnAnimationEndListener? = null,
) {
val roundnessAnimation =
@@ -364,6 +437,7 @@
ringerButtonUiModel.cornerRadius - (background as GradientDrawable).cornerRadius
val roundnessAnimationUpdateListener =
DynamicAnimation.OnAnimationUpdateListener { _, value, _ ->
+ onProgressChanged(value, cornerRadiusDiff > 0F)
(background as GradientDrawable).cornerRadius = radius + value * cornerRadiusDiff
background.invalidateSelf()
}
@@ -394,4 +468,9 @@
)
}
}
+
+ 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 0000000..6e3db0a
--- /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 e646636..627d75e 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.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 @@
@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 @@
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 @@
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 @@
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 @@
}
private fun VolumeDialogRingerModel.toViewModel(
- drawerState: RingerDrawerState
+ drawerState: RingerDrawerState,
+ isZenMuted: Boolean,
): RingerViewModelState {
val currentIndex = availableModes.indexOf(currentRingerMode)
if (currentIndex == -1) {
@@ -132,10 +141,11 @@
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 @@
private fun VolumeDialogRingerModel.toButtonViewModel(
ringerMode: RingerMode,
+ isZenMuted: Boolean,
isSelectedButton: Boolean = false,
): RingerButtonViewModel? {
return when (ringerMode.value) {
@@ -176,7 +187,7 @@
)
RINGER_MODE_NORMAL ->
when {
- isMuted && isEnabled ->
+ isMuted && !isZenMuted ->
RingerButtonViewModel(
imageResId =
if (isSelectedButton) {
@@ -226,7 +237,7 @@
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 @@
)
}
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 e52bad9..f305246 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 @@
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 @@
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 0000000..5c39b6f
--- /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 0000000..5750c04
--- /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 6dd5b63..2d56524 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.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 @@
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 @@
.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/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index e565de5..02747d7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,9 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import android.graphics.Color as GraphicsColor
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -77,7 +79,13 @@
ConnectedDeviceViewModel(
label = label,
labelColor =
- Color.Attribute(com.android.internal.R.attr.materialColorOnSurfaceVariant),
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(com.android.internal.R.attr.materialColorOnSurface)
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ },
deviceName =
if (mediaOutputModel.isInAudioSharing) {
context.getString(R.string.audio_sharing_description)
@@ -96,11 +104,7 @@
},
)
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
mediaOutputComponentInteractor.mediaOutputModel
@@ -121,7 +125,15 @@
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSurface
+ )
+ }
} else {
Color.Attribute(
com.android.internal.R.attr.materialColorSurfaceContainerHighest
@@ -129,7 +141,15 @@
},
backgroundColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSecondary)
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorSecondary
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
@@ -139,38 +159,29 @@
icon = icon,
iconColor =
if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- )
+ if (Flags.volumeRedesign()) {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorPrimary
+ )
+ } else {
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ )
+ }
} else {
Color.Attribute(com.android.internal.R.attr.materialColorOutline)
},
- backgroundColor =
- if (mediaOutputModel.canOpenAudioSwitcher) {
- Color.Attribute(com.android.internal.R.attr.materialColorSurface)
- } else {
- Color.Attribute(
- com.android.internal.R.attr.materialColorSurfaceContainerHighest
- )
- },
+ backgroundColor = Color.Loaded(GraphicsColor.TRANSPARENT),
)
}
}
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- null,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
val enabled: StateFlow<Boolean> =
mediaOutputComponentInteractor.mediaOutputModel
.filterData()
.map { it.canOpenAudioSwitcher }
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- true,
- )
+ .stateIn(coroutineScope, SharingStarted.Eagerly, true)
fun onBarClick(expandable: Expandable?) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
@@ -178,7 +189,7 @@
mediaOutputComponentInteractor.mediaOutputModel.value
actionsInteractor.onBarClick(
(result as? Result.Data<MediaOutputComponentModel>)?.data,
- expandable
+ expandable,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 073781e..0ec71c2 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.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.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 ae94544..a41725f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -90,6 +90,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.clearInvocations
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -528,6 +529,8 @@
fun listenForDnd_onDndChange_updatesClockZenMode() =
testScope.runTest {
underTest.listenForDnd(testScope.backgroundScope)
+ runCurrent()
+ clearInvocations(events)
zenModeRepository.activateMode(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 7c08928..11199cb 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.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 @@
//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 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 0000000..d7fcb6a
--- /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 2a6d29c..580f631 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.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 @@
}
@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() =
+ fun newEducationToastBeforeMaxToastsPerSessionTriggered() =
testScope.runTest {
setUpForDeviceConnection()
- tutorialSchedulerRepository.updateLaunchTime(DeviceType.TOUCHPAD, eduClock.instant())
+ setUpForInitialDelayElapse()
+ val model by collectLastValue(underTest.educationTriggered)
- val model by collectLastValue(repository.readGestureEduModelFlow(gestureType))
- val originalValue = model!!.signalCount
- eduClock.offset(initialDelayElapsedDuration)
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ triggerEducation(HOME)
- assertThat(model?.signalCount).isEqualTo(originalValue + 1)
+ assertThat(model).isEqualTo(EducationInfo(HOME, EducationUiType.Toast, userId = 0))
}
@Test
- fun dataUnchangedOnIncrementSignalCountBeforeInitialDelay() =
+ 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
- // No offset to the clock to simulate update before initial delay
- val listener = getOverviewProxyListener()
- listener.updateContextualEduStats(/* isTrackpadGesture= */ false, gestureType)
+ triggerEducation(OVERVIEW)
- assertThat(model?.signalCount).isEqualTo(originalValue)
+ // 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 dataUnchangedOnIncrementSignalCountWithoutOobeLaunchTime() =
+ fun newEducationToastAfterMinIntervalElapsedWhenMaxToastsPerSessionTriggered() =
testScope.runTest {
- // No update to OOBE launch time to simulate no OOBE is launched yet
setUpForDeviceConnection()
+ 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
- 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 newEducationNotificationAfterMaxToastsPerSessionTriggered() =
+ testScope.runTest {
+ setUpForDeviceConnection()
+ setUpForInitialDelayElapse()
+ val models by collectValues(underTest.educationTriggered.filterNotNull())
+ triggerEducation(BACK)
+
+ // 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)
+
+ 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 @@
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 @@
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/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 194f456..38acd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -137,6 +137,7 @@
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wallpapers.data.repository.FakeWallpaperRepository;
+import com.android.window.flags.Flags;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -157,6 +158,10 @@
@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
+
+ private static final boolean ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS =
+ Flags.ensureKeyguardDoesTransitionStarting();
+
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardViewMediator mViewMediator;
@@ -1164,6 +1169,29 @@
*/
private void assertATMSLockScreenShowing(boolean showing)
throws RemoteException {
+
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ // ATMS is called via bgExecutor, so make sure to run all of those calls first.
+ processAllMessagesAndBgExecutorMessages();
+
+ final InOrder orderedSetLockScreenShownCalls = inOrder(mKeyguardTransitions);
+ final ArgumentCaptor<Boolean> showingCaptor = ArgumentCaptor.forClass(Boolean.class);
+ orderedSetLockScreenShownCalls
+ .verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(showingCaptor.capture(), anyBoolean());
+
+ // The captor will have the most recent startKeyguardTransition call's value.
+ assertEquals(showing, showingCaptor.getValue());
+
+ // We're now just after the last startKeyguardTransition call. If we expect the
+ // lockscreen to be showing, ensure that we didn't subsequently ask for it to go away.
+ if (showing) {
+ orderedSetLockScreenShownCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(false), anyBoolean());
+ }
+ return;
+ }
+
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
@@ -1192,6 +1220,20 @@
// ATMS is called via bgExecutor, so make sure to run all of those calls first.
processAllMessagesAndBgExecutorMessages();
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ final InOrder orderedGoingAwayCalls = inOrder(mKeyguardTransitions);
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, atLeastOnce())
+ .startKeyguardTransition(eq(false) /* keyguardShowing */,
+ eq(false) /* aodShowing */);
+
+ // Advance the inOrder to just past the last goingAway call. Let's make sure we didn't
+ // re-show the lockscreen, which would cancel going away.
+ orderedGoingAwayCalls.verify(mKeyguardTransitions, never())
+ .startKeyguardTransition(eq(true) /* keyguardShowing */,
+ anyBoolean() /* aodShowing */);
+ return;
+ }
+
final InOrder orderedGoingAwayCalls = inOrder(mActivityTaskManagerService);
orderedGoingAwayCalls.verify(mActivityTaskManagerService, atLeastOnce())
.keyguardGoingAway(anyInt());
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 e7fb470c..c410111 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.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.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 @@
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 @@
underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -207,6 +216,7 @@
val underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -231,6 +241,7 @@
val underTest =
GlanceableHubContainerController(
communalInteractor,
+ kosmos.communalSettingsInteractor,
communalViewModel,
keyguardInteractor,
kosmos.keyguardTransitionInteractor,
@@ -631,7 +642,7 @@
}
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
@@ -688,7 +699,7 @@
}
}
- @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
@@ -721,11 +732,13 @@
}
}
- @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/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 7634490..728f418 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -655,14 +655,14 @@
// CDMA roaming is off, GSM roaming is off
whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, false, false, false)
)
assertThat(latest).isFalse()
// CDMA roaming is off, GSM roaming is on
cb.onDisplayInfoChanged(
- TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true)
+ TelephonyDisplayInfo(NETWORK_TYPE_LTE, NETWORK_TYPE_UNKNOWN, true, false, false)
)
assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
index cc0597b..76fc611 100644
--- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -30,6 +30,7 @@
import android.os.IBinder
import android.os.UserHandle
import android.util.ArrayMap
+import android.view.Display
import android.view.KeyEvent
import com.android.internal.logging.InstanceId
import com.android.internal.statusbar.IAddTileResultCallback
@@ -95,6 +96,90 @@
)
)
+ var statusBarIconsSecondaryDisplay =
+ ArrayMap<String, StatusBarIcon>().also {
+ it["slot1"] = mock<StatusBarIcon>()
+ it["slot2"] = mock<StatusBarIcon>()
+ }
+ var disabledFlags1SecondaryDisplay = 12345678
+ var appearanceSecondaryDisplay = 1234
+ var appearanceRegionsSecondaryDisplay =
+ arrayOf(
+ AppearanceRegion(
+ /* appearance = */ 123,
+ /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+ ),
+ AppearanceRegion(
+ /* appearance = */ 345,
+ /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+ ),
+ )
+ var imeWindowVisSecondaryDisplay = 9876
+ var imeBackDispositionSecondaryDisplay = 654
+ var showImeSwitcherSecondaryDisplay = true
+ var disabledFlags2SecondaryDisplay = 87654321
+ var navbarColorManagedByImeSecondaryDisplay = true
+ var behaviorSecondaryDisplay = 234
+ var requestedVisibleTypesSecondaryDisplay = 345
+ var packageNameSecondaryDisplay = "fake.bar.ser.vice"
+ var transientBarTypesSecondaryDisplay = 0
+ var letterboxDetailsSecondaryDisplay =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds = */ Rect(
+ /* left= */ 5,
+ /* top= */ 6,
+ /* right= */ 7,
+ /* bottom= */ 8,
+ ),
+ /* letterboxFullBounds = */ Rect(
+ /* left= */ 1,
+ /* top= */ 2,
+ /* right= */ 3,
+ /* bottom= */ 4,
+ ),
+ /* appAppearance = */ 123,
+ )
+ )
+
+ private val defaultRegisterStatusBarResult
+ get() =
+ RegisterStatusBarResult(
+ statusBarIcons,
+ disabledFlags1,
+ appearance,
+ appearanceRegions,
+ imeWindowVis,
+ imeBackDisposition,
+ showImeSwitcher,
+ disabledFlags2,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibleTypes,
+ packageName,
+ transientBarTypes,
+ letterboxDetails,
+ )
+
+ private val registerStatusBarResultSecondaryDisplay
+ get() =
+ RegisterStatusBarResult(
+ statusBarIconsSecondaryDisplay,
+ disabledFlags1SecondaryDisplay,
+ appearanceSecondaryDisplay,
+ appearanceRegionsSecondaryDisplay,
+ imeWindowVisSecondaryDisplay,
+ imeBackDispositionSecondaryDisplay,
+ showImeSwitcherSecondaryDisplay,
+ disabledFlags2SecondaryDisplay,
+ navbarColorManagedByImeSecondaryDisplay,
+ behaviorSecondaryDisplay,
+ requestedVisibleTypesSecondaryDisplay,
+ packageNameSecondaryDisplay,
+ transientBarTypesSecondaryDisplay,
+ letterboxDetailsSecondaryDisplay,
+ )
+
override fun expandNotificationsPanel() {}
override fun collapsePanels() {}
@@ -140,21 +225,16 @@
override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
registeredStatusBar = callbacks
- return RegisterStatusBarResult(
- statusBarIcons,
- disabledFlags1,
- appearance,
- appearanceRegions,
- imeWindowVis,
- imeBackDisposition,
- showImeSwitcher,
- disabledFlags2,
- navbarColorManagedByIme,
- behavior,
- requestedVisibleTypes,
- packageName,
- transientBarTypes,
- letterboxDetails,
+ return defaultRegisterStatusBarResult
+ }
+
+ override fun registerStatusBarForAllDisplays(
+ callbacks: IStatusBar
+ ): Map<String, RegisterStatusBarResult> {
+ registeredStatusBar = callbacks
+ return mapOf(
+ DEFAULT_DISPLAY_ID.toString() to defaultRegisterStatusBarResult,
+ SECONDARY_DISPLAY_ID.toString() to registerStatusBarResultSecondaryDisplay,
)
}
@@ -352,4 +432,9 @@
override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
override fun showRearDisplayDialog(currentBaseState: Int) {}
+
+ companion object {
+ const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
+ const val SECONDARY_DISPLAY_ID = 2
+ }
}
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
similarity index 63%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
index 0589bf8..470a8e4 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintManagerKosmos.kt
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.biometrics
-/**
- * @hide
- */
-parcelable FeatureDetails;
+import android.hardware.fingerprint.FingerprintManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.fingerprintManager by Kosmos.Fixture { mock<FingerprintManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
index ae592b9..646c190 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt
@@ -17,20 +17,19 @@
package com.android.systemui.biometrics.domain.interactor
import android.content.applicationContext
-import android.hardware.fingerprint.FingerprintManager
import com.android.systemui.biometrics.authController
+import com.android.systemui.biometrics.fingerprintManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
val Kosmos.udfpsOverlayInteractor by Fixture {
UdfpsOverlayInteractor(
context = applicationContext,
authController = authController,
selectedUserInteractor = selectedUserInteractor,
- fingerprintManager = mock<FingerprintManager>(),
+ fingerprintManager = fingerprintManager,
scope = applicationCoroutineScope,
)
}
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 1f68195..ad92b31 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.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.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.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 @@
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/education/data/repository/ContextualEducationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryKosmos.kt
index 1df3ef4..1021169 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/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
similarity index 66%
copy from core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
copy to packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
index 0589bf8..63bfa52 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
@@ -5,7 +5,7 @@
* 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
+ * 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,
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package android.app.ondeviceintelligence;
+package com.android.systemui.globalactions
-/**
- * @hide
- */
-parcelable FeatureDetails;
+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 552cd94..4cb8a41 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.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.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 @@
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 10b073e..2e6d8ed 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.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 @@
{ 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 c574463..d71bc31 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.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 @@
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 4acedaa..322b412 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 @@
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 c951642..40c3c96 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.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/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
index b8d3ff4..f412d98 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/compose/PaginatedGridLayoutKosmos.kt
@@ -14,16 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.compose
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
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.editModeButtonViewModelFactory by
- Kosmos.Fixture {
- object : EditModeButtonViewModel.Factory {
- override fun create(): EditModeButtonViewModel {
- return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
- }
- }
- }
+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
similarity index 89%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
index 6fe860c..f57806c 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 33227a4..86c3add 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.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 5c71ba2..128cfca 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.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 @@
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 9481fca..e79d213 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.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
similarity index 88%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
index b8d3ff4..8ae1332 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 0000000..52d8a3a
--- /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 597d52d..bc1c60c 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 @@
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/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
index 2ecfb45..3c37101 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorKosmos.kt
@@ -18,6 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
import javax.inject.Provider
@@ -26,5 +27,6 @@
ModesTileUserActionInteractor(
qsTileIntentUserInputHandler,
Provider { modesDialogDelegate }.get(),
+ zenModeInteractor,
)
}
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 6afc0d80..aa6ce9a 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.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 @@
tileGridViewModel,
editModeViewModel,
detailsViewModel,
+ toolbarViewModelFactory,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 8712b02..22f8767 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -34,12 +34,12 @@
const val DISPLAY_ID = Display.DEFAULT_DISPLAY
}
- override val defaultDisplay: FakeStatusBarModePerDisplayRepository =
- FakeStatusBarModePerDisplayRepository()
+ private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarModePerDisplayRepository>()
- override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository {
- return defaultDisplay
- }
+ override val defaultDisplay: FakeStatusBarModePerDisplayRepository = forDisplay(DISPLAY_ID)
+
+ override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository =
+ perDisplayRepos.computeIfAbsent(displayId) { FakeStatusBarModePerDisplayRepository() }
}
class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository {
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 0000000..fa5bd7a
--- /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 0000000..4a249a8
--- /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,
+ )
+ }
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 3963d7c..766b280 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 @@
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/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
index b8d3ff4..8f9184d 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/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.statusbar.policy
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
-val Kosmos.editModeButtonViewModelFactory by
- Kosmos.Fixture {
- object : EditModeButtonViewModel.Factory {
- override fun create(): EditModeButtonViewModel {
- return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
- }
- }
- }
+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/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
index 68d08e2..bbccbb1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt
@@ -20,6 +20,7 @@
import com.android.settingslib.notification.modes.zenIconLoader
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository
import com.android.systemui.statusbar.policy.data.repository.deviceProvisioningRepository
@@ -32,6 +33,7 @@
zenModeRepository = zenModeRepository,
notificationSettingsRepository = notificationSettingsRepository,
bgDispatcher = testDispatcher,
+ backgroundScope = backgroundScope,
iconLoader = zenIconLoader,
deviceProvisioningRepository = deviceProvisioningRepository,
userSetupRepository = userSetupRepository,
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 c8ba551..34661ce 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.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 @@
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 a5e1335..feaa6d2 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -25,6 +25,10 @@
static_libs: ["libprotobuf-java-nano"],
},
},
+ apex_available: [
+ "com.android.neuralnetworks",
+ "//apex_available:platform",
+ ],
}
java_library_static {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 66c8d0f..59043a83 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -182,21 +182,6 @@
visibility: [":__subpackages__"],
}
-// Separated out from ravenwood-junit-impl since it needs to compile
-// against `module_current`
-java_library {
- name: "ravenwood-junit-impl-flag",
- srcs: [
- "junit-flag-src/**/*.java",
- ],
- sdk_version: "module_current",
- libs: [
- "junit",
- "flag-junit",
- ],
- visibility: ["//visibility:public"],
-}
-
// Carefully compiles against only module_current to support tests that
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
@@ -651,7 +636,6 @@
"flag-junit",
"ravenwood-framework",
"ravenwood-junit-impl",
- "ravenwood-junit-impl-flag",
"mockito-ravenwood-prebuilt",
"inline-mockito-ravenwood-prebuilt",
diff --git a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java b/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
deleted file mode 100644
index 9d62774..0000000
--- a/ravenwood/junit-flag-src/android/platform/test/flag/junit/RavenwoodFlagsValueProvider.java
+++ /dev/null
@@ -1,54 +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.platform.test.flag.junit;
-
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.IFlagsValueProvider;
-
-/**
- * Offer to create {@link CheckFlagsRule} instances that are useful on the Ravenwood deviceless
- * testing environment.
- *
- * At the moment, default flag values are not available on Ravenwood, so the only options offered
- * here are "all-on" and "all-off" options. Tests that want to exercise specific flag states should
- * use {@link android.platform.test.flag.junit.SetFlagsRule}.
- */
-public class RavenwoodFlagsValueProvider {
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-on" state.
- */
- public static CheckFlagsRule createAllOnCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return true;
- }
- });
- }
-
- /**
- * Create a {@link CheckFlagsRule} instance where flags are in an "all-off" state.
- */
- public static CheckFlagsRule createAllOffCheckFlagsRule() {
- return new CheckFlagsRule(new IFlagsValueProvider() {
- @Override
- public boolean getBoolean(String flag) {
- return false;
- }
- });
- }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 9eff20a..a332633 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -132,36 +132,27 @@
@Override
public Looper getMainLooper() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getLooper();
}
@Override
public Handler getMainThreadHandler() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getThreadHandler();
}
@Override
public Executor getMainExecutor() {
- Objects.requireNonNull(mMainThread,
- "Test must request setProvideMainThread() via RavenwoodConfig");
return mMainThread.getThreadExecutor();
}
@Override
public String getPackageName() {
- return Objects.requireNonNull(mPackageName,
- "Test must request setPackageName() (or setTargetPackageName())"
- + " via RavenwoodConfig");
+ return mPackageName;
}
@Override
public String getOpPackageName() {
- return Objects.requireNonNull(mPackageName,
- "Test must request setPackageName() via RavenwoodConfig");
+ return mPackageName;
}
@Override
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
deleted file mode 100644
index 3ed0f50..0000000
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ /dev/null
@@ -1,139 +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.platform.test.ravenwood;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
- */
-@Deprecated
-public final class RavenwoodConfig {
- /**
- * Use this to mark a field as the configuration.
- * @hide
- */
- @Target({ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Config {
- }
-
- /**
- * Stores internal states / methods associated with this config that's only needed in
- * junit-impl.
- */
- private RavenwoodConfig() {
- }
-
- /**
- * Return if the current process is running on a Ravenwood test environment.
- */
- public static boolean isOnRavenwood() {
- return RavenwoodRule.isOnRavenwood();
- }
-
- public static class Builder {
- private final RavenwoodConfig mConfig = new RavenwoodConfig();
-
- public Builder() {
- }
-
- /**
- * @deprecated no longer used. We always use an app UID.
- */
- @Deprecated
- public Builder setProcessSystem() {
- return this;
- }
-
- /**
- * @deprecated no longer used. We always use an app UID.
- */
- @Deprecated
- public Builder setProcessApp() {
- return this;
- }
-
- /**
- * @deprecated no longer used. Package name is set in the build file. (for now)
- */
- @Deprecated
- public Builder setPackageName(@NonNull String packageName) {
- return this;
- }
-
- /**
- * @deprecated no longer used. Package name is set in the build file. (for now)
- */
- @Deprecated
- public Builder setTargetPackageName(@NonNull String packageName) {
- return this;
- }
-
-
- /**
- * @deprecated no longer used. Target SDK level is set in the build file. (for now)
- */
- @Deprecated
- public Builder setTargetSdkLevel(int sdkLevel) {
- return this;
- }
-
- /**
- * @deprecated no longer used. Main thread is always available.
- */
- @Deprecated
- public Builder setProvideMainThread(boolean provideMainThread) {
- return this;
- }
-
- /**
- * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyImmutable(String, Object)}
- */
- @Deprecated
- public Builder setSystemPropertyImmutable(@NonNull String key,
- @Nullable Object value) {
- return this;
- }
-
- /**
- * @deprecated Use {@link RavenwoodRule.Builder#setSystemPropertyMutable(String, Object)}
- */
- @Deprecated
- public Builder setSystemPropertyMutable(@NonNull String key,
- @Nullable Object value) {
- return this;
- }
-
- /**
- * @deprecated no longer used. All supported services are available.
- */
- @Deprecated
- public Builder setServicesRequired(@NonNull Class<?>... services) {
- return this;
- }
-
- public RavenwoodConfig build() {
- return mConfig;
- }
- }
-}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d8cde0e..ffe5f6c 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -36,10 +36,8 @@
import java.util.regex.Pattern;
/**
- * @deprecated This class is undergoing a major change. Reach out to g/ravenwood if you need
- * any featues in it.
+ * Reach out to g/ravenwood if you need any features in it.
*/
-@Deprecated
public final class RavenwoodRule implements TestRule {
private static final String TAG = com.android.ravenwood.common.RavenwoodCommonUtils.TAG;
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.java
deleted file mode 100644
index 306c2b39..0000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodConfigTest.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.ravenwoodtest.bivalenttest;
-
-import static android.platform.test.ravenwood.RavenwoodConfig.isOnRavenwood;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test to make sure the config field is used.
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodConfigTest {
- private static final String PACKAGE_NAME = "com.android.ravenwoodtest.bivalenttest";
-
- @Test
- public void testConfig() {
- assumeTrue(isOnRavenwood());
- assertEquals(PACKAGE_NAME,
- InstrumentationRegistry.getInstrumentation().getContext().getPackageName());
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index fc0bb33..a7cb9bb 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -210,6 +210,35 @@
},
}
+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 @@
"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 f13e229..c17c340 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 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 @@
} catch (SecurityException exception) {
safeExecuteAppFunctionCallback.onError(
new AppFunctionException(
- AppFunctionException.ERROR_DENIED,
- exception.getMessage()));
+ AppFunctionException.ERROR_DENIED, exception.getMessage()));
return null;
}
@@ -195,12 +194,12 @@
@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 @@
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 @@
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 5393b93..6191767 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 @@
@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 e85a70d..69481c3 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.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.os.Binder;
import android.os.Process;
import android.os.UserHandle;
-import android.os.UserManager;
import com.android.internal.infra.AndroidFuture;
@@ -124,8 +124,7 @@
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 @@
}
@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 @@
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/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 4bcfb33..7c8604f 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 @@
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 ffa259b..9e64559 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -127,6 +127,7 @@
"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,6 +158,7 @@
"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",
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index d84a892..ccc44a4 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -16,14 +16,9 @@
package com.android.server;
-import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
-
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,7 +33,6 @@
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
-import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
@@ -47,12 +41,10 @@
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.util.MutableBoolean;
import android.util.Slog;
import android.view.KeyEvent;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
@@ -78,8 +70,7 @@
* Time in milliseconds in which the power button must be pressed twice so it will be considered
* as a camera launch.
*/
- @VisibleForTesting
- static final long POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
+ @VisibleForTesting static final long CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS = 300;
/**
@@ -109,23 +100,10 @@
@VisibleForTesting
static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
- /** Indicates camera should be launched on power double tap. */
- @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
-
- /** Indicates wallet should be launched on power double tap. */
- @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
-
- /** Number of taps required to launch the double tap shortcut (either camera or wallet). */
- public static final int DOUBLE_POWER_TAP_COUNT_THRESHOLD = 2;
-
- /** Bundle to send with PendingIntent to grant background activity start privileges. */
- private static final Bundle GRANT_BACKGROUND_START_PRIVILEGES =
- ActivityOptions.makeBasic()
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS)
- .toBundle();
-
- private final QuickAccessWalletClient mQuickAccessWalletClient;
+ /**
+ * Number of taps required to launch camera shortcut.
+ */
+ private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
/** The listener that receives the gesture event. */
private final GestureEventListener mGestureListener = new GestureEventListener();
@@ -180,9 +158,6 @@
*/
private boolean mCameraDoubleTapPowerEnabled;
- /** Whether wallet double tap power button gesture is currently enabled. */
- private boolean mWalletDoubleTapPowerEnabled;
-
/**
* Whether emergency gesture is currently enabled
*/
@@ -229,22 +204,14 @@
}
}
public GestureLauncherService(Context context) {
- this(
- context,
- new MetricsLogger(),
- QuickAccessWalletClient.create(context),
- new UiEventLoggerImpl());
+ this(context, new MetricsLogger(), new UiEventLoggerImpl());
}
@VisibleForTesting
- public GestureLauncherService(
- Context context,
- MetricsLogger metricsLogger,
- QuickAccessWalletClient quickAccessWalletClient,
+ GestureLauncherService(Context context, MetricsLogger metricsLogger,
UiEventLogger uiEventLogger) {
super(context);
mContext = context;
- mQuickAccessWalletClient = quickAccessWalletClient;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
}
@@ -270,9 +237,6 @@
"GestureLauncherService");
updateCameraRegistered();
updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
updateEmergencyGestureEnabled();
updateEmergencyGesturePowerButtonCooldownPeriodMs();
@@ -328,14 +292,6 @@
}
@VisibleForTesting
- void updateWalletDoubleTapPowerEnabled() {
- boolean enabled = isWalletDoubleTapPowerSettingEnabled(mContext, mUserId);
- synchronized (this) {
- mWalletDoubleTapPowerEnabled = enabled;
- }
- }
-
- @VisibleForTesting
void updateEmergencyGestureEnabled() {
boolean enabled = isEmergencyGestureSettingEnabled(mContext, mUserId);
synchronized (this) {
@@ -462,33 +418,10 @@
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
- /** Checks if camera should be launched on double press of the power button. */
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
- boolean res;
-
- if (launchWalletOptionOnPowerDoubleTap()) {
- res = isDoubleTapPowerGestureSettingEnabled(context, userId)
- && getDoubleTapPowerGestureAction(context, userId)
- == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
- } else {
- // These are legacy settings that will be deprecated once the option to launch both
- // wallet and camera has been created.
- res = isCameraDoubleTapPowerEnabled(context.getResources())
- && (Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
- }
- return res;
- }
-
- /** Checks if wallet should be launched on double tap of the power button. */
- public static boolean isWalletDoubleTapPowerSettingEnabled(Context context, int userId) {
- if (!launchWalletOptionOnPowerDoubleTap()) {
- return false;
- }
-
- return isDoubleTapPowerGestureSettingEnabled(context, userId)
- && getDoubleTapPowerGestureAction(context, userId)
- == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
+ return isCameraDoubleTapPowerEnabled(context.getResources())
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
public static boolean isCameraLiftTriggerSettingEnabled(Context context, int userId) {
@@ -508,28 +441,6 @@
isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
- private static int getDoubleTapPowerGestureAction(Context context, int userId) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
- userId);
- }
-
- /** Whether the shortcut to launch app on power double press is enabled. */
- private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
- isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
- userId)
- == 1;
- }
-
- private static boolean isDoubleTapConfigEnabled(Resources resources) {
- return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
- }
-
/**
* Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
* value is capped at a maximum
@@ -583,56 +494,10 @@
* Whether GestureLauncherService should be enabled according to system properties.
*/
public static boolean isGestureLauncherEnabled(Resources resources) {
- boolean res =
- isCameraLaunchEnabled(resources)
- || isCameraLiftTriggerEnabled(resources)
- || isEmergencyGestureEnabled(resources);
- if (launchWalletOptionOnPowerDoubleTap()) {
- res |= isDoubleTapConfigEnabled(resources);
- } else {
- res |= isCameraDoubleTapPowerEnabled(resources);
- }
- return res;
- }
-
- /**
- * Processes a power key event in GestureLauncherService without performing an action. This
- * method is called on every KEYCODE_POWER ACTION_DOWN event and ensures that, even if
- * KEYCODE_POWER events are passed to and handled by the app, the GestureLauncherService still
- * keeps track of all running KEYCODE_POWER events for its gesture detection and relevant
- * actions.
- */
- public void processPowerKeyDown(KeyEvent event) {
- if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
- && event.getEventTime() - mLastEmergencyGestureTriggered
- < mEmergencyGesturePowerButtonCooldownPeriodMs) {
- return;
- }
- if (event.isLongPress()) {
- return;
- }
-
- final long powerTapInterval;
-
- synchronized (this) {
- powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
- // Tap too slow, reset consecutive tap counts.
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
- // Tap too slow for shortcuts
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps++;
- } else if (powerTapInterval > 0) {
- // Fast consecutive tap
- mPowerButtonConsecutiveTaps++;
- mPowerButtonSlowConsecutiveTaps++;
- }
- }
+ return isCameraLaunchEnabled(resources)
+ || isCameraDoubleTapPowerEnabled(resources)
+ || isCameraLiftTriggerEnabled(resources)
+ || isEmergencyGestureEnabled(resources);
}
/**
@@ -642,8 +507,8 @@
* @param outLaunched true if some action is taken as part of the key intercept (eg, app launch)
* @return true if the key down event is intercepted
*/
- public boolean interceptPowerKeyDown(
- KeyEvent event, boolean interactive, MutableBoolean outLaunched) {
+ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
+ MutableBoolean outLaunched) {
if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
&& event.getEventTime() - mLastEmergencyGestureTriggered
< mEmergencyGesturePowerButtonCooldownPeriodMs) {
@@ -665,7 +530,6 @@
return false;
}
boolean launchCamera = false;
- boolean launchWallet = false;
boolean launchEmergencyGesture = false;
boolean intercept = false;
long powerTapInterval;
@@ -677,12 +541,12 @@
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
+ } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) {
// Tap too slow for shortcuts
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
- } else if (!overridePowerKeyBehaviorInFocusedWindow() || powerTapInterval > 0) {
+ } else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
@@ -722,16 +586,10 @@
}
}
if (mCameraDoubleTapPowerEnabled
- && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
+ && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS
+ && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) {
launchCamera = true;
intercept = interactive;
- } else if (launchWalletOptionOnPowerDoubleTap()
- && mWalletDoubleTapPowerEnabled
- && powerTapInterval < POWER_DOUBLE_TAP_MAX_TIME_MS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
- launchWallet = true;
- intercept = interactive;
}
}
if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) {
@@ -750,10 +608,6 @@
(int) powerTapInterval);
mUiEventLogger.log(GestureLauncherEvent.GESTURE_CAMERA_DOUBLE_TAP_POWER);
}
- } else if (launchWallet) {
- Slog.i(TAG, "Power button double tap gesture detected, launching wallet. Interval="
- + powerTapInterval + "ms");
- launchWallet = sendGestureTargetActivityPendingIntent();
} else if (launchEmergencyGesture) {
Slog.i(TAG, "Emergency gesture detected, launching.");
launchEmergencyGesture = handleEmergencyGesture();
@@ -769,74 +623,11 @@
mPowerButtonSlowConsecutiveTaps);
mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval);
- outLaunched.value = launchCamera || launchEmergencyGesture || launchWallet;
+ outLaunched.value = launchCamera || launchEmergencyGesture;
// Intercept power key event if the press is part of a gesture (camera, eGesture) and the
// user has completed setup.
return intercept && isUserSetupComplete();
}
-
- /**
- * Fetches and sends gestureTargetActivityPendingIntent from QuickAccessWallet, which is a
- * specific activity that QuickAccessWalletService has defined to be launch on detection of the
- * power button gesture.
- */
- private boolean sendGestureTargetActivityPendingIntent() {
- boolean userSetupComplete = isUserSetupComplete();
- if (mQuickAccessWalletClient == null
- || !mQuickAccessWalletClient.isWalletServiceAvailable()) {
- Slog.w(TAG, "QuickAccessWalletService is not available, ignoring wallet gesture.");
- return false;
- }
-
- if (!userSetupComplete) {
- if (DBG) {
- Slog.d(TAG, "userSetupComplete = false, ignoring wallet gesture.");
- }
- return false;
- }
- if (DBG) {
- Slog.d(TAG, "userSetupComplete = true, performing wallet gesture.");
- }
-
- mQuickAccessWalletClient.getGestureTargetActivityPendingIntent(
- getContext().getMainExecutor(),
- gesturePendingIntent -> {
- if (gesturePendingIntent == null) {
- Slog.d(TAG, "getGestureTargetActivityPendingIntent is null.");
- sendFallbackPendingIntent();
- return;
- }
- sendPendingIntentWithBackgroundStartPrivileges(gesturePendingIntent);
- });
- return true;
- }
-
- /**
- * If gestureTargetActivityPendingIntent is null, this method is invoked to start the activity
- * that QuickAccessWalletService has defined to host the Wallet view, which is typically the
- * home screen of the Wallet application.
- */
- private void sendFallbackPendingIntent() {
- mQuickAccessWalletClient.getWalletPendingIntent(
- getContext().getMainExecutor(),
- walletPendingIntent -> {
- if (walletPendingIntent == null) {
- Slog.w(TAG, "getWalletPendingIntent returns null. Not launching "
- + "anything for wallet.");
- return;
- }
- sendPendingIntentWithBackgroundStartPrivileges(walletPendingIntent);
- });
- }
-
- private void sendPendingIntentWithBackgroundStartPrivileges(PendingIntent pendingIntent) {
- try {
- pendingIntent.send(GRANT_BACKGROUND_START_PRIVILEGES);
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "PendingIntent was canceled", e);
- }
- }
-
/**
* @return true if camera was launched, false otherwise.
*/
@@ -909,39 +700,31 @@
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
}
- private final BroadcastReceiver mUserReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
- mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
- mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
- registerContentObservers();
- updateCameraRegistered();
- updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
- updateEmergencyGestureEnabled();
- updateEmergencyGesturePowerButtonCooldownPeriodMs();
- }
- }
- };
+ private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
+ mUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
+ registerContentObservers();
+ updateCameraRegistered();
+ updateCameraDoubleTapPowerEnabled();
+ updateEmergencyGestureEnabled();
+ updateEmergencyGesturePowerButtonCooldownPeriodMs();
+ }
+ }
+ };
- private final ContentObserver mSettingObserver =
- new ContentObserver(new Handler()) {
- public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
- if (userId == mUserId) {
- updateCameraRegistered();
- updateCameraDoubleTapPowerEnabled();
- if (launchWalletOptionOnPowerDoubleTap()) {
- updateWalletDoubleTapPowerEnabled();
- }
- updateEmergencyGestureEnabled();
- updateEmergencyGesturePowerButtonCooldownPeriodMs();
- }
- }
- };
+ private final ContentObserver mSettingObserver = new ContentObserver(new Handler()) {
+ public void onChange(boolean selfChange, android.net.Uri uri, int userId) {
+ if (userId == mUserId) {
+ updateCameraRegistered();
+ updateCameraDoubleTapPowerEnabled();
+ updateEmergencyGestureEnabled();
+ updateEmergencyGesturePowerButtonCooldownPeriodMs();
+ }
+ }
+ };
private final class GestureEventListener implements SensorEventListener {
@Override
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e57b009..eba9a25 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2166,7 +2166,11 @@
overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
}
boolean isRoaming = telephonyDisplayInfo.isRoaming();
- return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
+ boolean isNtn = telephonyDisplayInfo.isNtn();
+ boolean isSatelliteConstrainedData =
+ telephonyDisplayInfo.isSatelliteConstrainedData();
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming,
+ isNtn, isSatelliteConstrainedData);
}
public void notifyCallForwardingChanged(boolean cfi) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3ce6451..719928b 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 @@
: 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 @@
if (!accountExistsCache(accounts, account)) {
return;
}
- setUserdataInternal(accounts, account, key, value);
+ setUserdataInternal(accounts, account, key, value, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2932,7 +2940,7 @@
}
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 @@
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 ebe7fa5..cd929c1 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.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.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 @@
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 @@
// 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 @@
}
@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 @@
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 c27126a..aea24d9 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1101,6 +1101,9 @@
/** 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;
@@ -1168,7 +1171,8 @@
.setMinConsumedPowerThreshold(minConsumedPowerThreshold);
if (isBatteryUsageStatsAccumulationSupported()) {
- query.accumulated();
+ query.accumulated()
+ .setMaxStatsAgeMs(BATTERY_USAGE_STATS_PER_UID_MAX_STATS_AGE);
}
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 d0153d8..883e09f 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 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;
@@ -146,7 +147,9 @@
"aaos_window_triage",
"aaos_audio_triage",
"aaos_power_triage",
+ "aaos_storage_triage",
"aaos_sdv",
+ "aaos_vac_triage",
"accessibility",
"android_core_networking",
"android_health_services",
@@ -213,6 +216,7 @@
"pixel_bluetooth",
"pixel_connectivity_gps",
"pixel_continuity",
+ "pixel_display",
"pixel_perf",
"pixel_sensors",
"pixel_state_server",
@@ -462,16 +466,38 @@
* @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;
}
@@ -490,9 +516,9 @@
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;
}
@@ -500,10 +526,10 @@
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;
}
}
@@ -644,15 +670,8 @@
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) {
@@ -762,15 +781,8 @@
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/content/SyncLogger.java b/services/core/java/com/android/server/content/SyncLogger.java
index fc20ef20..7154bc4 100644
--- a/services/core/java/com/android/server/content/SyncLogger.java
+++ b/services/core/java/com/android/server/content/SyncLogger.java
@@ -73,6 +73,8 @@
*/
public static synchronized SyncLogger getInstance() {
if (sInstance == null) {
+ // Always default to the sync logger for now (see b/381957278#comment8).
+ /*
final String flag = SystemProperties.get("debug.synclog");
final boolean enable =
(Build.IS_DEBUGGABLE
@@ -83,6 +85,8 @@
} else {
sInstance = new SyncLogger();
}
+ */
+ sInstance = new SyncLogger();
}
return sInstance;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5c62995..452dc5f 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.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.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 @@
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 @@
}
dpc.onSwitchUser(newUserId, userSerial, newBrightness);
});
- handleSettingsChange();
+ handleMinimalPostProcessingAllowedSettingChange();
+
+ if (mFlags.isDisplayContentModeManagementEnabled()) {
+ updateMirrorBuiltInDisplaySettingLocked();
+ }
}
}
@@ -825,12 +833,15 @@
// 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 @@
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/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 440a271..860be20 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -32,6 +32,7 @@
import android.provider.DeviceConfigInterface;
import android.util.IndentingPrintWriter;
import android.util.Spline;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
@@ -58,6 +59,7 @@
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final LightSensorController mLightSensorController;
+ private int mDisplayState = Display.STATE_OFF;
private final ClamperChangeListener mClamperChangeListenerExternal;
private final Executor mExecutor;
@@ -149,16 +151,13 @@
public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
DisplayManagerInternal.DisplayPowerRequest request,
float brightnessValue, boolean slowChange, int displayState) {
+ mDisplayState = displayState;
DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
displayBrightnessState);
builder.setIsSlowChange(slowChange);
builder.setBrightness(brightnessValue);
- if (displayState != STATE_ON) {
- mLightSensorController.stop();
- } else {
- adjustLightSensorSubscription();
- }
+ adjustLightSensorSubscription();
for (int i = 0; i < mModifiers.size(); i++) {
mModifiers.get(i).apply(request, builder);
@@ -230,7 +229,8 @@
}
private void adjustLightSensorSubscription() {
- if (mModifiers.stream().anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
+ if (mDisplayState == STATE_ON && mModifiers.stream()
+ .anyMatch(BrightnessStateModifier::shouldListenToLightSensor)) {
mLightSensorController.restart();
} else {
mLightSensorController.stop();
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 45106f5..585fc44 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 @@
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 @@
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 @@
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 0000000..c58ba55
--- /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 2bcea77..dd3415f 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 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 @@
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 @@
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 @@
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/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 265e453..bc44fed 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.graphics.PointF;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
@@ -47,6 +48,12 @@
public abstract void setDisplayViewports(List<DisplayViewport> viewports);
/**
+ * Called by {@link com.android.server.display.DisplayManagerService} to inform InputManager
+ * about changes in the displays topology.
+ */
+ public abstract void setDisplayTopology(DisplayTopology topology);
+
+ /**
* Called by the power manager to tell the input manager whether it should start
* watching for wake events on given displays.
*
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 767a723..aee5e7f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -51,6 +51,7 @@
import android.hardware.SensorPrivacyManager.Sensors;
import android.hardware.SensorPrivacyManagerInternal;
import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayTopology;
import android.hardware.display.DisplayViewport;
import android.hardware.input.AidlInputGestureData;
import android.hardware.input.HostUsiVersion;
@@ -660,6 +661,10 @@
mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId());
}
+ private void setDisplayTopologyInternal(DisplayTopology topology) {
+ mNative.setDisplayTopology(topology.getGraph());
+ }
+
/**
* Gets the current state of a key or button by key code.
* @param deviceId The input device id, or -1 to consult all devices.
@@ -3461,6 +3466,11 @@
}
@Override
+ public void setDisplayTopology(DisplayTopology topology) {
+ setDisplayTopologyInternal(topology);
+ }
+
+ @Override
public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
boolean globallyInteractive = false;
ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 935f0ff..c72f7c0 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.display.DisplayTopologyGraph;
import android.hardware.display.DisplayViewport;
import android.hardware.input.InputSensorInfo;
import android.hardware.lights.Light;
@@ -42,6 +43,8 @@
void setDisplayViewports(DisplayViewport[] viewports);
+ void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
int getScanCodeState(int deviceId, int sourceMask, int scanCode);
int getKeyCodeState(int deviceId, int sourceMask, int keyCode);
@@ -323,6 +326,9 @@
public native void setDisplayViewports(DisplayViewport[] viewports);
@Override
+ public native void setDisplayTopology(DisplayTopologyGraph topologyGraph);
+
+ @Override
public native int getScanCodeState(int deviceId, int sourceMask, int scanCode);
@Override
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 aa215ce..4a533f4 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -20,7 +20,6 @@
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubMessage;
-import android.hardware.contexthub.HubServiceInfo;
import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
import android.hardware.location.IContextHubTransactionCallback;
@@ -28,6 +27,10 @@
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;
/**
@@ -61,6 +64,20 @@
/** 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,
@@ -81,25 +98,30 @@
}
@Override
- public int openSession(HubEndpointInfo destination, HubServiceInfo serviceInfo)
+ public int openSession(HubEndpointInfo destination, String serviceDescriptor)
throws RemoteException {
ContextHubServiceUtil.checkPermissions(mContext);
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,
- serviceInfo == null ? null : serviceInfo.getServiceDescriptor());
- } 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
@@ -115,11 +137,6 @@
}
@Override
- public void openSessionRequestComplete(int sessionId) {
- // TODO(b/378487799): Implement this
- }
-
- @Override
public void unregister() {
ContextHubServiceUtil.checkPermissions(mContext);
mIsRegistered.set(false);
@@ -128,11 +145,34 @@
} 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
@@ -154,4 +194,53 @@
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 fb64f99..8c5095f3 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 @@
*
* @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 @@
/** 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 @@
/** 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 @@
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 @@
}
}
+ /**
+ * @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 c05f7a0..9d52c6a 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 @@
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 @@
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 @@
}
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 @@
@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 0d926b9..0b47a61 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 @@
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 @@
}
try {
mContextHubWrapper.registerEndpointCallback(
- new ContextHubHalEndpointCallback(mHubInfoRegistry));
+ new ContextHubHalEndpointCallback(mHubInfoRegistry, mEndpointManager));
} catch (RemoteException | UnsupportedOperationException e) {
Log.e(TAG, "Exception while registering IEndpointCallback", e);
}
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 d2b2331..b912492 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -87,6 +87,12 @@
}
}
+ public HubEndpointInfo getEndpointInfo(HubEndpointInfo.HubEndpointIdentifier id) {
+ synchronized (mLock) {
+ return mHubEndpointInfos.get(id);
+ }
+ }
+
/** Invoked when HAL restarts */
public void onHalRestart() {
synchronized (mLock) {
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 c79dc84..6cb9429 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 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 @@
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/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 0f65d1d..2686f2b 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -46,6 +46,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.media.BluetoothRouteController.NoOpBluetoothRouteController;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -378,7 +379,12 @@
Slog.e(
TAG,
"Could not map this selected device attribute type to an available route: "
- + selectedDeviceAttributesType);
+ + selectedDeviceAttributesType
+ + ". Available types: "
+ + Arrays.toString(
+ Arrays.stream(audioDeviceInfos)
+ .map(AudioDeviceInfo::getType)
+ .toArray()));
// We know mRouteIdToAvailableDeviceRoutes is not empty.
newSelectedRouteHolder = mRouteIdToAvailableDeviceRoutes.values().iterator().next();
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index ab68ed3..abc067d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2347,13 +2347,24 @@
if (!Flags.enableRouteVisibilityControlApi()) {
return true;
}
- for (String permission : route.getRequiredPermissions()) {
- if (mContext.checkPermission(permission, mPid, mUid)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
+ List<Set<String>> permissionSets = route.getRequiredPermissions();
+ if (permissionSets.isEmpty()) {
+ return true;
+ }
+ for (Set<String> permissionSet : permissionSets) {
+ boolean hasAllInSet = true;
+ for (String permission : permissionSet) {
+ if (mContext.checkPermission(permission, mPid, mUid)
+ != PackageManager.PERMISSION_GRANTED) {
+ hasAllInSet = false;
+ break;
+ }
+ }
+ if (hasAllInSet) {
+ return true;
}
}
- return true;
+ return false;
}
}
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 4479e9d..1673b8e 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -30,7 +30,9 @@
import android.media.quality.PictureProfile;
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;
@@ -45,8 +47,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.stream.Collectors;
import java.util.UUID;
+import java.util.stream.Collectors;
/**
* This service manage picture profile and sound profile for TV setting. Also communicates with the
@@ -80,7 +82,7 @@
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();
@@ -99,12 +101,12 @@
}
@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();
@@ -117,7 +119,8 @@
}
@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};
@@ -143,7 +146,8 @@
}
@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,
@@ -151,18 +155,19 @@
}
@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);
@@ -173,12 +178,17 @@
}
@Override
- public PictureProfileHandle getPictureProfileHandle(String id, int userId) {
- return null;
+ public List<PictureProfileHandle> getPictureProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
}
@Override
- public SoundProfile createSoundProfile(SoundProfile sp, int userId) {
+ public List<SoundProfileHandle> getSoundProfileHandle(String[] id, UserHandle user) {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
@@ -197,12 +207,12 @@
}
@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();
@@ -215,9 +225,10 @@
}
@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 (
@@ -241,7 +252,8 @@
}
@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,
@@ -249,18 +261,19 @@
}
@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);
@@ -352,7 +365,8 @@
getName(cursor),
getInputId(cursor),
getPackageName(cursor),
- jsonToBundle(getSettingsString(cursor))
+ jsonToBundle(getSettingsString(cursor)),
+ PictureProfileHandle.NONE
);
}
@@ -363,7 +377,8 @@
getName(cursor),
getInputId(cursor),
getPackageName(cursor),
- jsonToBundle(getSettingsString(cursor))
+ jsonToBundle(getSettingsString(cursor)),
+ SoundProfileHandle.NONE
);
}
@@ -448,70 +463,71 @@
}
@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 15af36b..39eea74 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 @@
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 dc173b1..95aff56 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.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 @@
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/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index f79d9ef..65a38ae 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -123,13 +123,6 @@
}
flag {
- name: "use_ipcdatacache_channels"
- namespace: "systemui"
- description: "Adds an IPCDataCache for notification channel/group lookups"
- bug: "331677193"
-}
-
-flag {
name: "use_ssm_user_switch_signal"
namespace: "systemui"
description: "This flag controls which signal is used to handle a user switch system event"
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 09774f7..0000000
--- 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 8ec7160..871d12e 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.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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 58b1e49..be2f58d 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.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 @@
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/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 673a102..c0ddebe 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -129,14 +129,14 @@
}
}
- void notifySessionComplete(int sessionId, boolean success) {
+ void notifySessionComplete(int sessionId) {
if (DEBUG) {
- Slog.d(TAG, "Session complete for " + sessionId + " result: " + success);
+ Slog.d(TAG, "Session complete for " + sessionId);
}
synchronized (mTrackers) {
List<DependencyInstallTracker> completedTrackers = new ArrayList<>();
for (DependencyInstallTracker tracker: mTrackers) {
- if (!tracker.onSessionComplete(sessionId, success)) {
+ if (!tracker.onSessionComplete(sessionId)) {
completedTrackers.add(tracker);
}
}
@@ -354,7 +354,7 @@
// Don't wait for sessions that finished already
if (sessionInfo == null) {
Binder.withCleanCallingIdentity(() -> {
- notifySessionComplete(sessionId, /*success=*/ true);
+ notifySessionComplete(sessionId);
});
}
}
@@ -459,19 +459,13 @@
*
* Returns true if we still need to continue tracking.
*/
- public boolean onSessionComplete(int sessionId, boolean success) {
+ public boolean onSessionComplete(int sessionId) {
synchronized (this) {
if (!mPendingSessionIds.contains(sessionId)) {
// This had no impact on tracker, so continue tracking
return true;
}
- if (!success) {
- // If one of the dependency fails, the orig session would fail too.
- onError(mCallback, "Failed to install all dependencies");
- return false; // No point in tracking anymore
- }
-
mPendingSessionIds.remove(sessionId);
if (mPendingSessionIds.isEmpty()) {
mCallback.onResult(null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 516b002..e1fcc66 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -2330,8 +2330,7 @@
}
if (Flags.sdkDependencyInstaller()) {
- mInstallDependencyHelper.notifySessionComplete(
- session.sessionId, success);
+ mInstallDependencyHelper.notifySessionComplete(session.sessionId);
}
final File appIconFile = buildAppIconFile(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index 4b82de0..ef49f49 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.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 @@
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 @@
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));
+ });
}
- invokeCallback(callback, newIntent);
- }));
+ for (int i = 0; i < target.size(); i++) {
+ Pair<IRemoteCallback, Intent> p = target.get(i);
+ invokeCallback(p.first, p.second);
+ }
+ });
}
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 854e142..ec91da9 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 @@
* 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 @@
}
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/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 94b49e5..1fda478 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/policy/EventLogTags.logtags b/services/core/java/com/android/server/policy/EventLogTags.logtags
index 7563382..a4b6472 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 e39906b..f1a4811 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -88,12 +88,10 @@
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
-import static com.android.server.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -434,16 +432,6 @@
"android.intent.action.VOICE_ASSIST_RETAIL";
/**
- * Maximum amount of time in milliseconds between consecutive power onKeyDown events to be
- * considered a multi-press, only used for the power button.
- * Note: To maintain backwards compatibility for the power button, we are measuring the times
- * between consecutive down events instead of the first tap's up event and the second tap's
- * down event.
- */
- @VisibleForTesting public static final int POWER_MULTI_PRESS_TIMEOUT_MILLIS =
- ViewConfiguration.getMultiPressTimeout();
-
- /**
* Lock protecting internal state. Must not call out into window
* manager with lock held. (This lock will be acquired in places
* where the window manager is calling in with its own lock held.)
@@ -504,32 +492,6 @@
private WindowWakeUpPolicy mWindowWakeUpPolicy;
- /**
- * The three variables below are used for custom power key gesture detection in
- * PhoneWindowManager. They are used to detect when the power button has been double pressed
- * and, when it does happen, makes the behavior overrideable by the app.
- *
- * We cannot use the {@link PowerKeyRule} for this because multi-press power gesture detection
- * and behaviors are handled by {@link com.android.server.GestureLauncherService}, and the
- * {@link PowerKeyRule} only handles single and long-presses of the power button. As a result,
- * overriding the double tap behavior requires custom gesture detection here that mimics the
- * logic in {@link com.android.server.GestureLauncherService}.
- *
- * Long-term, it would be beneficial to move all power gesture detection to
- * {@link PowerKeyRule} so that this custom logic isn't required.
- */
- // Time of last power down event.
- private long mLastPowerDown;
-
- // Number of power button events consecutively triggered (within a specific timeout threshold).
- private int mPowerButtonConsecutiveTaps = 0;
-
- // Whether a double tap of the power button has been detected.
- volatile boolean mDoubleTapPowerDetected;
-
- // Runnable that is queued on a delay when the first power keyDown event is sent to the app.
- private Runnable mPowerKeyDelayedRunnable = null;
-
boolean mSafeMode;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -1135,11 +1097,6 @@
mPowerKeyHandled = mPowerKeyHandled || hungUp
|| handledByPowerManager || isKeyGestureTriggered
|| mKeyCombinationManager.isPowerKeyIntercepted();
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mPowerKeyHandled |= mDoubleTapPowerDetected;
- }
-
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromWakeKey(event);
@@ -2712,19 +2669,7 @@
if (mShouldEarlyShortPressOnPower) {
return;
}
- // TODO(b/380433365): Remove deferring single power press action when refactoring.
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, 1 /*count*/, displayId);
- });
- } else {
- powerPress(downTime, 1 /*count*/, displayId);
- }
-
+ powerPress(downTime, 1 /*count*/, displayId);
}
@Override
@@ -2755,17 +2700,7 @@
@Override
void onMultiPress(long downTime, int count, int displayId) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, count, displayId);
- });
- } else {
- powerPress(downTime, count, displayId);
- }
+ powerPress(downTime, count, displayId);
}
@Override
@@ -3542,12 +3477,6 @@
}
}
- if (overridePowerKeyBehaviorInFocusedWindow() && event.getKeyCode() == KEYCODE_POWER
- && event.getAction() == KeyEvent.ACTION_UP
- && mDoubleTapPowerDetected) {
- mDoubleTapPowerDetected = false;
- }
-
return needToConsumeKey ? keyConsumed : keyNotConsumed;
}
@@ -4063,8 +3992,6 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
case KeyEvent.KEYCODE_SCREENSHOT:
if (firstDown) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
@@ -4120,8 +4047,6 @@
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -4132,90 +4057,6 @@
return (metaState & KeyEvent.META_META_ON) != 0;
}
- /**
- * Called by interceptKeyBeforeDispatching to handle interception logic for KEYCODE_POWER
- * KeyEvents.
- *
- * @return true if intercepting the key, false if sending to app.
- */
- private boolean interceptPowerKeyBeforeDispatching(IBinder focusedToken, KeyEvent event) {
- if (!overridePowerKeyBehaviorInFocusedWindow()) {
- //Flag disabled: intercept the power key and do not send to app.
- return true;
- }
- if (event.getKeyCode() != KEYCODE_POWER) {
- Log.wtf(TAG, "interceptPowerKeyBeforeDispatching received a non-power KeyEvent "
- + "with key code: " + event.getKeyCode());
- return false;
- }
-
- // Intercept keys (don't send to app) for 3x, 4x, 5x gestures)
- if (mPowerButtonConsecutiveTaps > DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- // UP key; just reuse the original decision.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- final Set<Integer> consumedKeys = mConsumedKeysForDevice.get(event.getDeviceId());
- return consumedKeys != null
- && consumedKeys.contains(event.getKeyCode());
- }
-
- KeyInterceptionInfo info =
- mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
-
- if (info == null || !mButtonOverridePermissionChecker.canWindowOverridePowerKey(mContext,
- info.windowOwnerUid, info.inputFeaturesFlags)) {
- // The focused window does not have the permission to override power key behavior.
- if (DEBUG_INPUT) {
- String interceptReason = "";
- if (info == null) {
- interceptReason = "Window is null";
- } else if (!mButtonOverridePermissionChecker.canAppOverrideSystemKey(mContext,
- info.windowOwnerUid)) {
- interceptReason = "Application does not have "
- + "OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission";
- } else {
- interceptReason = "Window does not have inputFeatureFlag set";
- }
-
- Log.d(TAG, String.format("Intercepting KEYCODE_POWER event. action=%d, "
- + "eventTime=%d to window=%s. interceptReason=%s. "
- + "mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), (info != null)
- ? info.windowTitle : "null", interceptReason,
- mDoubleTapPowerDetected));
- }
- // Intercept the key (i.e. do not send to app)
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- if (DEBUG_INPUT) {
- Log.d(TAG, String.format("Sending KEYCODE_POWER to app. action=%d, "
- + "eventTime=%d to window=%s. mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), info.windowTitle,
- mDoubleTapPowerDetected));
- }
-
- if (!mDoubleTapPowerDetected) {
- //Single press: post a delayed runnable for the single press power action that will be
- // called if it's not cancelled by a double press.
- final var downTime = event.getDownTime();
- mPowerKeyDelayedRunnable = () ->
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, downTime);
- mHandler.postDelayed(mPowerKeyDelayedRunnable, POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- } else if (mPowerKeyDelayedRunnable != null) {
- //Double press detected: cancel the single press runnable.
- mHandler.removeCallbacks(mPowerKeyDelayedRunnable);
- mPowerKeyDelayedRunnable = null;
- }
-
- // Focused window has permission. Send to app.
- return false;
- }
-
@SuppressLint("MissingPermission")
private void initKeyGestures() {
if (!useKeyGestureEventHandler()) {
@@ -4240,6 +4081,7 @@
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:
@@ -4323,6 +4165,7 @@
}
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(),
@@ -4790,11 +4633,6 @@
return true;
}
- if (overridePowerKeyBehaviorInFocusedWindow() && keyCode == KEYCODE_POWER) {
- handleUnhandledSystemKey(event);
- return true;
- }
-
if (useKeyGestureEventHandler()) {
return false;
}
@@ -5629,13 +5467,8 @@
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- result |= ACTION_PASS_TO_USER;
- } else {
- // Any activity on the power button stops the accessibility shortcut
- result &= ~ACTION_PASS_TO_USER;
- }
-
+ // Any activity on the power button stops the accessibility shortcut
+ result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
@@ -5897,32 +5730,6 @@
}
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- if (mGestureLauncherService != null) {
- mGestureLauncherService.processPowerKeyDown(event);
- }
-
- if (detectDoubleTapPower(event)) {
- mDoubleTapPowerDetected = true;
-
- // Copy of the event for handler in case the original event gets recycled.
- KeyEvent eventCopy = KeyEvent.obtain(event);
- mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_POWER,
- eventCopy.getEventTime(),
- () -> {
- if (!handleCameraGesture(eventCopy, interactive)) {
- mSingleKeyGestureDetector.interceptKey(
- eventCopy, interactive, defaultDisplayOn);
- } else {
- mSingleKeyGestureDetector.reset();
- }
- eventCopy.recycle();
- });
- return;
- }
- }
-
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
@@ -5934,27 +5741,6 @@
mSingleKeyGestureDetector.interceptKey(event, interactive, defaultDisplayOn);
}
- private boolean detectDoubleTapPower(KeyEvent event) {
- if (event.getKeyCode() != KEYCODE_POWER || event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
- if (event.isLongPress()) {
- return false;
- }
-
- final long powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_MULTI_PRESS_TIMEOUT_MILLIS) {
- // Tap too slow for double press
- mPowerButtonConsecutiveTaps = 1;
- } else {
- mPowerButtonConsecutiveTaps++;
- }
-
- return powerTapInterval < POWER_MULTI_PRESS_TIMEOUT_MILLIS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD;
- }
-
// The camera gesture will be detected by GestureLauncherService.
private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
// camera gesture.
@@ -7811,12 +7597,6 @@
null)
== PERMISSION_GRANTED;
}
-
- boolean canWindowOverridePowerKey(Context context, int uid, int inputFeaturesFlags) {
- return canAppOverrideSystemKey(context, uid)
- && (inputFeaturesFlags & WindowManager.LayoutParams
- .INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
- }
}
private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
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 63e8d99..8c588b4 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -195,23 +195,22 @@
mLastAccumulationMonotonicHistorySize = historySize;
}
- handler.post(() -> accumulateBatteryUsageStats(stats));
+ // No need to store the accumulated stats asynchronously, as the entire accumulation
+ // operation is async
+ handler.post(() -> accumulateBatteryUsageStats(stats, false));
}
/**
* Computes BatteryUsageStats for the period since the last accumulated stats were stored,
- * adds them to the accumulated stats and saves the result.
+ * adds them to the accumulated stats and asynchronously saves the result.
*/
public void accumulateBatteryUsageStats(BatteryStatsImpl stats) {
- AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+ accumulateBatteryUsageStats(stats, true);
+ }
- final BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includePowerStateData()
- .includeScreenStateData()
- .build();
- updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+ private void accumulateBatteryUsageStats(BatteryStatsImpl stats, boolean storeAsync) {
+ AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
PowerStatsSpan powerStatsSpan = new PowerStatsSpan(AccumulatedBatteryUsageStatsSection.ID);
powerStatsSpan.addSection(
@@ -220,8 +219,13 @@
accumulatedStats.startWallClockTime,
accumulatedStats.endMonotonicTime - accumulatedStats.startMonotonicTime);
mMonotonicClock.write();
- mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
- accumulatedStats.builder::discard);
+ if (storeAsync) {
+ mPowerStatsStore.storePowerStatsSpanAsync(powerStatsSpan,
+ accumulatedStats.builder::discard);
+ } else {
+ mPowerStatsStore.storePowerStatsSpan(powerStatsSpan);
+ accumulatedStats.builder.discard();
+ }
}
/**
@@ -269,7 +273,7 @@
BatteryUsageStats batteryUsageStats;
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
- batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+ batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query);
} else if (query.getAggregatedToTimestamp() == 0) {
BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
query.getMonotonicStartTime(),
@@ -288,9 +292,13 @@
}
private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
- BatteryUsageStatsQuery query, long currentTimeMs) {
+ BatteryUsageStatsQuery query) {
AccumulatedBatteryUsageStats accumulatedStats = loadAccumulatedBatteryUsageStats();
- updateAccumulatedBatteryUsageStats(accumulatedStats, stats, query);
+ if (accumulatedStats.endMonotonicTime == MonotonicClock.UNDEFINED
+ || mMonotonicClock.monotonicTime() - accumulatedStats.endMonotonicTime
+ > query.getMaxStatsAge()) {
+ updateAccumulatedBatteryUsageStats(accumulatedStats, stats);
+ }
return accumulatedStats.builder.build();
}
@@ -321,7 +329,7 @@
}
private void updateAccumulatedBatteryUsageStats(AccumulatedBatteryUsageStats accumulatedStats,
- BatteryStatsImpl stats, BatteryUsageStatsQuery query) {
+ BatteryStatsImpl stats) {
long startMonotonicTime = accumulatedStats.endMonotonicTime;
if (startMonotonicTime == MonotonicClock.UNDEFINED) {
startMonotonicTime = stats.getMonotonicStartTime();
@@ -333,6 +341,7 @@
accumulatedStats.builder = new BatteryUsageStats.Builder(
stats.getCustomEnergyConsumerNames(), true, true, true, 0);
accumulatedStats.startWallClockTime = stats.getStartClockTime();
+ accumulatedStats.startMonotonicTime = stats.getMonotonicStartTime();
accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
}
@@ -342,7 +351,7 @@
accumulatedStats.builder.setStatsDuration(endWallClockTime - startMonotonicTime);
mPowerAttributor.estimatePowerConsumption(accumulatedStats.builder, stats.getHistory(),
- startMonotonicTime, MonotonicClock.UNDEFINED);
+ startMonotonicTime, endMonotonicTime);
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 dcdd3bd..2609cf7 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,6 +23,7 @@
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;
@@ -169,6 +170,9 @@
}
}
}
+ 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/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e753ce8..1bed48a 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -163,6 +163,7 @@
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
private final Runnable mRunExpiration = this::runExpiration;
+ private final PackageWatchdog mPackageWatchdog;
// The # of milli-seconds to sleep for each received ACTION_PACKAGE_ENABLE_ROLLBACK.
// Used by #blockRollbackManager to test timeout in enabling rollbacks.
@@ -190,6 +191,7 @@
mPackageHealthObserver = new RollbackPackageHealthObserver(mContext);
mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller);
+ mPackageWatchdog = PackageWatchdog.getInstance(mContext);
// Kick off and start monitoring the handler thread.
HandlerThread handlerThread = new HandlerThread("RollbackManagerServiceHandler");
@@ -1249,12 +1251,12 @@
// should document in PackageInstaller.SessionParams#setEnableRollback
// After enabling and committing any rollback, observe packages and
// prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
} else {
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ mPackageWatchdog.startExplicitHealthCheck(mPackageHealthObserver,
+ rollback.getPackageNames(), mRollbackLifetimeDurationInMillis);
}
runExpiration();
}
@@ -1317,7 +1319,7 @@
}
});
- PackageWatchdog.getInstance(mContext).dump(ipw);
+ mPackageWatchdog.dump(ipw);
}
@AnyThread
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
index b9c8d3d..f51c25d 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -24,9 +24,14 @@
import android.content.Context;
import android.os.UserManager;
import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Slog;
+import java.util.ArrayList;
+import java.util.List;
+
/** @hide */
public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
private static final String TAG = "AdvancedProtectionDisallowCellular2G";
@@ -35,11 +40,13 @@
new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
private final DevicePolicyManager mDevicePolicyManager;
private final TelephonyManager mTelephonyManager;
+ private final SubscriptionManager mSubscriptionManager;
public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
super(context, enabled);
mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
setPolicy(enabled);
}
@@ -50,14 +57,44 @@
return mFeature;
}
+ private static boolean isEmbeddedSubscriptionVisible(SubscriptionInfo subInfo) {
+ if (subInfo.isEmbedded()
+ && (subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING
+ || (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()
+ && subInfo.isOnlyNonTerrestrialNetwork()))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private List<TelephonyManager> getActiveTelephonyManagers() {
+ List<TelephonyManager> telephonyManagers = new ArrayList<>();
+
+ for (SubscriptionInfo subInfo : mSubscriptionManager.getActiveSubscriptionInfoList()) {
+ if (isEmbeddedSubscriptionVisible(subInfo)) {
+ telephonyManagers.add(
+ mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()));
+ }
+ }
+
+ return telephonyManagers;
+ }
+
@Override
public boolean isAvailable() {
- return mTelephonyManager.isDataCapable();
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ if (telephonyManager.isDataCapable()
+ && telephonyManager.isRadioInterfaceCapabilitySupported(
+ mTelephonyManager.CAPABILITY_USES_ALLOWED_NETWORK_TYPES_BITMASK)) {
+ return true;
+ }
+ }
+
+ return false;
}
private void setPolicy(boolean enabled) {
- Slog.i(TAG, "setPolicy called with " + enabled);
-
if (enabled) {
Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
mDevicePolicyManager.addUserRestrictionGlobally(
@@ -75,12 +112,14 @@
// Leave 2G disabled even if APM is disabled.
if (!enabled) {
- long oldAllowedTypes =
- mTelephonyManager.getAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
- long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
- mTelephonyManager.setAllowedNetworkTypesForReason(
- TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ for (TelephonyManager telephonyManager : getActiveTelephonyManagers()) {
+ long oldAllowedTypes =
+ telephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ telephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
}
}
}
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 0ea88e8..687442b 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 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 @@
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 @@
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 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 61fac46..0bc4482 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 @@
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 dba7374..0000000
--- 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 b25656e..a16e66d 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionEventTransportConnection.java
@@ -24,42 +24,56 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.intrusiondetection.IIntrusionDetectionEventTransport;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.infra.AndroidFuture;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Process;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.TimeUnit;
public class IntrusionDetectionEventTransportConnection implements ServiceConnection {
+ 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 =
+ "debug.intrusiondetection_package_name";
+ private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 min
private static final String TAG = "IntrusionDetectionEventTransportConnection";
- private static final long FUTURE_TIMEOUT_MILLIS = 60 * 1000; // 1 mins
private final Context mContext;
private String mIntrusionDetectionEventTransportConfig;
volatile IIntrusionDetectionEventTransport mService;
+
public IntrusionDetectionEventTransportConnection(Context context) {
mContext = context;
- mService = null;
}
/**
* Initialize the IntrusionDetectionEventTransport binder service.
- * @return Whether the initialization succeed.
+ *
+ * @return Whether the initialization succeeds.
*/
public boolean initialize() {
+ Slog.d(TAG, "initialize");
if (!bindService()) {
return false;
}
+ // Wait for the service to be connected before calling initialize.
+ waitForConnection();
AndroidFuture<Boolean> resultFuture = new AndroidFuture<>();
try {
mService.initialize(resultFuture);
@@ -77,6 +91,20 @@
}
}
+ private void waitForConnection() {
+ synchronized (this) {
+ while (mService == null) {
+ Slog.d(TAG, "waiting for connection to service...");
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ /* never interrupted */
+ }
+ }
+ Slog.d(TAG, "connected to service");
+ }
+ }
+
/**
* Add data to the IntrusionDetectionEventTransport binder service.
* @param data List of IntrusionDetectionEvent.
@@ -118,11 +146,42 @@
}
}
+ private String getSystemPropertyValue(String propertyName) {
+ String commandString = "getprop " + propertyName;
+ try {
+ Process process = Runtime.getRuntime().exec(commandString);
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(process.getInputStream()));
+ String propertyValue = reader.readLine();
+ reader.close();
+ return propertyValue;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to get system property value:", e);
+ return null;
+ }
+ }
+
private boolean bindService() {
- mIntrusionDetectionEventTransportConfig = mContext.getString(
- com.android.internal.R.string.config_intrusionDetectionEventTransport);
+ String buildType = getSystemPropertyValue(PROPERTY_BUILD_TYPE);
+ mIntrusionDetectionEventTransportConfig =
+ mContext.getString(
+ com.android.internal.R.string.config_intrusionDetectionEventTransport);
+
+ // If the build type is not production, and a property value is set, use it instead.
+ // This allows us to test the service with a different config.
+ if (!buildType.equals(PRODUCTION_BUILD)
+ && !TextUtils.isEmpty(
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME))) {
+ mIntrusionDetectionEventTransportConfig =
+ getSystemPropertyValue(PROPERTY_INTRUSION_DETECTION_SERVICE_NAME);
+ }
+ Slog.d(
+ TAG,
+ "mIntrusionDetectionEventTransportConfig: "
+ + mIntrusionDetectionEventTransportConfig);
+
if (TextUtils.isEmpty(mIntrusionDetectionEventTransportConfig)) {
- Slog.e(TAG, "config_intrusionDetectionEventTransport is empty");
+ Slog.e(TAG, "Unable to find a valid config for the transport service");
return false;
}
@@ -163,11 +222,19 @@
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ synchronized (this) {
+ mService = IIntrusionDetectionEventTransport.Stub.asInterface(service);
+ Slog.d(TAG, "connected to service");
+ this.notifyAll();
+ }
}
@Override
public void onServiceDisconnected(ComponentName name) {
- mService = null;
+ synchronized (this) {
+ mService = null;
+ Slog.d(TAG, "disconnected from service");
+ this.notifyAll();
+ }
}
}
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 0287b41..8ff1c7f 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 @@
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 @@
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 1c93d3f..083b1fd 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();
+ if (mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already enabled");
+ return;
+ }
+ 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);
+ }
}
@Override
public void disable() {
- disableNetworkLog();
- }
-
- private void enableNetworkLog() {
- if (!isNetworkLogEnabled()) {
- mDpm.setNetworkLoggingEnabled(mAdmin, true);
+ if (!mIsNetworkLoggingEnabled.get()) {
+ Slog.w(TAG, "Network logging is already disabled");
+ return;
}
- }
-
- private void disableNetworkLog() {
- if (isNetworkLogEnabled()) {
- mDpm.setNetworkLoggingEnabled(mAdmin, false);
- }
- }
-
- 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);
- return;
- }
- if (events == null) {
- if (!isNetworkLogEnabled()) {
- Slog.w(TAG, "Network logging is disabled");
+ 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 c5f736e..5611905 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 @@
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 @@
@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/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 908f51b..f8877ad 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -129,6 +129,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -339,7 +341,11 @@
}
@Override
- public void onDisplayAdded(int displayId) {}
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLock) {
+ mDisplayUiState.put(displayId, new UiState());
+ }
+ }
@Override
public void onDisplayRemoved(int displayId) {
@@ -1710,8 +1716,6 @@
icons = new ArrayMap<>(mIcons);
}
synchronized (mLock) {
- // TODO(b/118592525): Currently, status bar only works on the default display.
- // Make it aware of multi-display if needed.
final UiState state = mDisplayUiState.get(DEFAULT_DISPLAY);
return new RegisterStatusBarResult(icons, gatherDisableActionsLocked(mCurrentUserId, 1),
state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
@@ -1722,6 +1726,46 @@
}
}
+ @Override
+ public Map<String, RegisterStatusBarResult> registerStatusBarForAllDisplays(IStatusBar bar) {
+ enforceStatusBarService();
+ enforceValidCallingUser();
+
+ Slog.i(TAG, "registerStatusBarForAllDisplays bar=" + bar);
+ mBar = bar;
+ mDeathRecipient.linkToDeath();
+ notifyBarAttachChanged();
+
+ synchronized (mLock) {
+ Map<String, RegisterStatusBarResult> results = new HashMap<>();
+
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ final int displayId = mDisplayUiState.keyAt(i);
+ final UiState state = mDisplayUiState.get(displayId);
+
+ final ArrayMap<String, StatusBarIcon> icons;
+ synchronized (mIcons) {
+ icons = new ArrayMap<>(mIcons);
+ }
+
+ if (state != null) {
+ results.put(String.valueOf(displayId),
+ new RegisterStatusBarResult(icons,
+ gatherDisableActionsLocked(mCurrentUserId, 1),
+ state.mAppearance, state.mAppearanceRegions,
+ state.mImeWindowVis,
+ state.mImeBackDisposition, state.mShowImeSwitcher,
+ gatherDisableActionsLocked(mCurrentUserId, 2),
+ state.mNavbarColorManagedByIme, state.mBehavior,
+ state.mRequestedVisibleTypes,
+ state.mPackageName, state.mTransientBarTypes,
+ state.mLetterboxDetails));
+ }
+ }
+ return results;
+ }
+ }
+
private void notifyBarAttachChanged() {
UiThread.getHandler().post(() -> {
if (mGlobalActionListener == null) return;
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index 63a3e5a..a38fc5b 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.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 @@
// 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/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0ebdaed..afa7ea1 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 @@
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 @@
.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 93ccd74..6ccceb9 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 @@
}
}
+ /** 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 e190963..0b66158 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1272,7 +1272,13 @@
@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;
@@ -2274,7 +2280,7 @@
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
- getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
+ setFixedTransformHint(getPendingTransaction(), mSurfaceControl, rotation);
// The sync transaction should already contains setDisplayProjection, so unset the
// hint to restore the natural state when the transaction is applied.
transaction.unsetFixedTransformHint(mSurfaceControl);
@@ -2284,6 +2290,10 @@
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
}
+ void setFixedTransformHint(Transaction t, SurfaceControl sc, int rotation) {
+ t.setFixedTransformHint(sc, (rotation + mDisplayInfo.installOrientation) % 4);
+ }
+
void configureDisplayPolicy() {
mRootWindowContainer.updateDisplayImePolicyCache();
mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
@@ -7109,9 +7119,11 @@
/**
* @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
- if (mRequestedVisibleTypes != requestedVisibleTypes) {
- mRequestedVisibleTypes = requestedVisibleTypes;
+ void updateRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+ int newRequestedVisibleTypes =
+ (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
+ if (mRequestedVisibleTypes != newRequestedVisibleTypes) {
+ mRequestedVisibleTypes = newRequestedVisibleTypes;
}
}
}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2a5a3a5..1c4e487 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 @@
}
// 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 27f82d9..7fdc2c6 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.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 @@
// 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/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index c20b858..8f0f686 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -56,7 +56,7 @@
mOldRotation = oldRotation;
mNewRotation = newRotation;
mApplyFixedTransformHint = applyFixedTransformationHint;
- mFixedTransformHint = oldRotation;
+ mFixedTransformHint = (oldRotation + info.installOrientation) % 4;
final boolean flipped = info.rotation == ROTATION_90 || info.rotation == ROTATION_270;
final int pH = flipped ? info.logicalWidth : info.logicalHeight;
final int pW = flipped ? info.logicalHeight : info.logicalWidth;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f090ef1..810aa04 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3425,6 +3425,7 @@
info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
+ info.isActivityStackTransparent = !topTask.forAllActivities(r -> (r.occludesParent()));
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
final WindowState windowState = top != null
? top.findMainWindow(/* includeStartingApp= */ false) : null;
@@ -4488,7 +4489,7 @@
}
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 8562bb2..f3c03cb 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 @@
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/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2397e03..aa60f93 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3733,7 +3733,7 @@
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
- getPendingTransaction().setFixedTransformHint(mSurfaceControl,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
t.setMatrix(mSurfaceControl, 1, 0, 0, 1);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 00ade80..bf4cb45 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4671,7 +4671,8 @@
@EnforcePermission(android.Manifest.permission.MANAGE_APP_TOKENS)
@Override
public void updateDisplayWindowRequestedVisibleTypes(int displayId,
- @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token statsToken) {
+ @InsetsType int visibleTypes, @InsetsType int mask,
+ @Nullable ImeTracker.Token statsToken) {
updateDisplayWindowRequestedVisibleTypes_enforcePermission();
final long origId = Binder.clearCallingIdentity();
try {
@@ -4684,7 +4685,7 @@
}
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_UPDATE_DISPLAY_WINDOW_REQUESTED_VISIBLE_TYPES);
- dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
+ dc.mRemoteInsetsControlTarget.updateRequestedVisibleTypes(visibleTypes, mask);
// TODO(b/353463205) the statsToken shouldn't be null as it is used later in the
// IME provider. Check if we have to create a new request here, if null.
dc.getInsetsStateController().onRequestedVisibleTypesChanged(
@@ -9019,16 +9020,19 @@
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);
}
}
@@ -9060,8 +9064,15 @@
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 ddff24d..66921ff 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 @@
}
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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 447d443..cebe790 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5750,10 +5750,9 @@
|| mKeyInterceptionInfo.layoutParamsPrivateFlags != getAttrs().privateFlags
|| mKeyInterceptionInfo.layoutParamsType != getAttrs().type
|| mKeyInterceptionInfo.windowTitle != getWindowTag()
- || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()
- || mKeyInterceptionInfo.inputFeaturesFlags != getAttrs().inputFeatures) {
+ || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()) {
mKeyInterceptionInfo = new KeyInterceptionInfo(getAttrs().type, getAttrs().privateFlags,
- getWindowTag().toString(), getOwningUid(), getAttrs().inputFeatures);
+ getWindowTag().toString(), getOwningUid());
}
return mKeyInterceptionInfo;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 004f406..832295a 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -629,7 +629,7 @@
.build();
t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
t.reparent(getSurfaceControl(), leash);
- getPendingTransaction().setFixedTransformHint(leash,
+ mDisplayContent.setFixedTransformHint(getPendingTransaction(), leash,
getWindowConfiguration().getDisplayRotation());
mFixedRotationTransformLeash = leash;
updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5a45730..0464230 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -68,6 +68,7 @@
#include <map>
#include <vector>
+#include "android_hardware_display_DisplayTopology.h"
#include "android_hardware_display_DisplayViewport.h"
#include "android_hardware_input_InputApplicationHandle.h"
#include "android_hardware_input_InputWindowHandle.h"
@@ -321,6 +322,8 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
+ void setDisplayTopology(JNIEnv* env, jobject topologyGraph);
+
base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name);
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
const std::string& name,
@@ -640,6 +643,11 @@
InputReaderConfiguration::Change::DISPLAY_INFO);
}
+void NativeInputManager::setDisplayTopology(JNIEnv* env, jobject topologyGraph) {
+ android_hardware_display_DisplayTopologyGraph_toNative(env, topologyGraph);
+ // TODO(b/367661489): Use the topology
+}
+
base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChannel(
const std::string& name) {
ATRACE_CALL();
@@ -2092,6 +2100,12 @@
im->setDisplayViewports(env, viewportObjArray);
}
+static void nativeSetDisplayTopology(JNIEnv* env, jobject nativeImplObj,
+ jobject displayTopologyObj) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setDisplayTopology(env, displayTopologyObj);
+}
+
static jint nativeGetScanCodeState(JNIEnv* env, jobject nativeImplObj, jint deviceId,
jint sourceMask, jint scanCode) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3148,6 +3162,8 @@
{"start", "()V", (void*)nativeStart},
{"setDisplayViewports", "([Landroid/hardware/display/DisplayViewport;)V",
(void*)nativeSetDisplayViewports},
+ {"setDisplayTopology", "(Landroid/hardware/display/DisplayTopologyGraph;)V",
+ (void*)nativeSetDisplayTopology},
{"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
{"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
{"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index df37ec3..09fd8d4 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -49,6 +49,7 @@
int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
int register_android_server_SyntheticPasswordManager(JNIEnv* env);
int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_hardware_display_DisplayTopology(JNIEnv* env);
int register_android_server_am_OomConnection(JNIEnv* env);
int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_Freezer(JNIEnv* env);
@@ -114,6 +115,7 @@
register_android_server_storage_AppFuse(env);
register_android_server_SyntheticPasswordManager(env);
register_android_hardware_display_DisplayViewport(env);
+ register_android_hardware_display_DisplayTopology(env);
register_android_server_am_OomConnection(env);
register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_Freezer(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4d318f90..2627895 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 @@
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 @@
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* who */ null,
SET_TIME,
- caller.getPackageName(),
+ callerPackageName,
UserHandle.USER_ALL
);
Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
@@ -9197,7 +9197,7 @@
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 @@
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* who */ null,
SET_TIME_ZONE,
- caller.getPackageName(),
+ callerPackageName,
UserHandle.USER_ALL
);
Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index e4db4bd..543e32f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -672,10 +672,6 @@
}
}
- void saveToXml(TypedXmlSerializer serializer) throws IOException {
- mPolicyKey.saveToXml(serializer);
- }
-
@Nullable
static <V> PolicyDefinition<V> readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index a4fa089..0d9dbaa 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -19,7 +19,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.PolicyValue;
-import android.app.admin.flags.Flags;
import android.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
@@ -41,7 +40,6 @@
private static final String TAG = "PolicyState";
private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
@@ -225,12 +223,6 @@
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- if (!Flags.dontWritePolicyDefinition()) {
- serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- mPolicyDefinition.saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
- }
-
if (mCurrentResolvedPolicy != null) {
serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aa63c4a..65315af 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.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 @@
"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";
@@ -3453,7 +3454,7 @@
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 4412968..0d222fb 100644
--- a/services/java/com/android/server/flags.aconfig
+++ b/services/java/com/android/server/flags.aconfig
@@ -10,14 +10,6 @@
}
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 977bd19..0e1f68e 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/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index 3093c42..0ccaa60 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -29,11 +29,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.UserHandle;
import android.util.SparseArray;
import com.android.internal.R;
@@ -63,11 +65,13 @@
private final SparseArray<SupervisionUserData> mUserData = new SparseArray<>();
private final DevicePolicyManagerInternal mDpmInternal;
+ private final PackageManager mPackageManager;
private final UserManagerInternal mUserManagerInternal;
public SupervisionService(Context context) {
mContext = context.createAttributionContext(LOG_TAG);
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
+ mPackageManager = context.getPackageManager();
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
}
@@ -148,12 +152,13 @@
/** Returns whether the supervision app has profile owner status. */
private boolean isProfileOwner(@UserIdInt int userId) {
ComponentName profileOwner = mDpmInternal.getProfileOwnerAsUser(userId);
- if (profileOwner == null) {
- return false;
- }
+ return profileOwner != null && isSupervisionAppPackage(profileOwner.getPackageName());
+ }
- String configPackage = mContext.getResources().getString(R.string.config_systemSupervision);
- return profileOwner.getPackageName().equals(configPackage);
+ /** Returns whether the given package name belongs to the supervision role holder. */
+ private boolean isSupervisionAppPackage(String packageName) {
+ return packageName.equals(
+ mContext.getResources().getString(R.string.config_systemSupervision));
}
public static class Lifecycle extends SystemService {
@@ -211,6 +216,21 @@
private final class SupervisionManagerInternalImpl extends SupervisionManagerInternal {
@Override
+ public boolean isActiveSupervisionApp(int uid) {
+ String[] packages = mPackageManager.getPackagesForUid(uid);
+ if (packages == null) {
+ return false;
+ }
+ for (var packageName : packages) {
+ if (SupervisionService.this.isSupervisionAppPackage(packageName)) {
+ int userId = UserHandle.getUserId(uid);
+ return SupervisionService.this.isSupervisionEnabledForUser(userId);
+ }
+ }
+ return false;
+ }
+
+ @Override
public boolean isSupervisionEnabledForUser(@UserIdInt int userId) {
return SupervisionService.this.isSupervisionEnabledForUser(userId);
}
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 5492ba6..6e14bad 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.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 @@
/**
* Test class for
- * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader,
+ * {@link MethodDescriptorParser#parseMethodDescriptor(ClassLoader,
* MethodDescriptor)}.
* <p>
* Build/Install/Run:
@@ -119,13 +120,13 @@
}
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/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index 66e9c98..238654d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -126,16 +127,11 @@
@Test
public void testConstructor_doesNotStartsLightSensorController() {
- verify(mMockLightSensorController, never()).restart();
- }
-
- @Test
- public void testConstructor_startsLightSensorController() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
mClamperController = createBrightnessClamperController();
- verify(mMockLightSensorController).restart();
+ verify(mMockLightSensorController, never()).restart();
}
@Test
@@ -171,20 +167,43 @@
@Test
public void testOnDisplayChanged_doesNotRestartLightSensor() {
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
}
@Test
public void testOnDisplayChanged_restartsLightSensor() {
when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_ON);
+ reset(mMockLightSensorController);
+
mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+ verify(mMockLightSensorController, never()).stop();
verify(mMockLightSensorController).restart();
}
@Test
+ public void testOnDisplayChanged_doesNotRestartLightSensor_screenOff() {
+ when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
+ mClamperController.clamp(mDisplayBrightnessState, mMockRequest, 0.1f,
+ false, STATE_OFF);
+ reset(mMockLightSensorController);
+
+ mClamperController.onDisplayChanged(mMockDisplayDeviceData);
+
+ verify(mMockLightSensorController, never()).restart();
+ verify(mMockLightSensorController).stop();
+ }
+
+ @Test
public void testClamp_AppliesModifier() {
float initialBrightness = 0.2f;
boolean initialSlowChange = true;
diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp
index a31a3fb..f235da2 100644
--- a/services/tests/ondeviceintelligencetests/Android.bp
+++ b/services/tests/ondeviceintelligencetests/Android.bp
@@ -44,14 +44,18 @@
"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 09774f7..a4fc758 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 d12579c..28ccb84 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 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 @@
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 @@
@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 @@
@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 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 709f83b..73dcfe7 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,6 +36,7 @@
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;
@@ -81,8 +82,9 @@
.setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
private MockClock mMockClock = mStatsRule.getMockClock();
- private MonotonicClock mMonotonicClock = new MonotonicClock(666777, mMockClock);
+ private MonotonicClock mMonotonicClock = mStatsRule.getMonotonicClock();
private Context mContext;
+ private PowerStatsStore mPowerStatsStore;
@Before
public void setup() throws IOException {
@@ -93,6 +95,9 @@
} else {
mContext = InstrumentationRegistry.getContext();
}
+ mPowerStatsStore = spy(new PowerStatsStore(
+ new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
+ mStatsRule.getHandler()));
}
@Test
@@ -274,10 +279,7 @@
powerAttributor.setPowerComponentSupported(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
true);
- BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext,
- powerAttributor, mStatsRule.getPowerProfile(),
- mStatsRule.getCpuScalingPolicies(), mock(PowerStatsStore.class), 0, mMockClock,
- mMonotonicClock);
+ BatteryUsageStatsProvider provider = createBatteryUsageStatsProvider(0);
return provider.getBatteryUsageStats(batteryStats, BatteryUsageStatsQuery.DEFAULT);
}
@@ -331,30 +333,30 @@
BatteryStats.HistoryItem item;
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_RESET, BatteryStats.HistoryItem.EVENT_NONE,
null, 0, 3_600_000, 90, 1_000_000);
assertThat(item = iterator.next()).isNotNull();
- assertHistoryItem(item,
+ assertHistoryItem(batteryStats, 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(item,
+ assertHistoryItem(batteryStats, 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(item,
+ assertHistoryItem(batteryStats, 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(item,
+ assertHistoryItem(batteryStats, item,
BatteryStats.HistoryItem.CMD_UPDATE,
BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
"foo", APP_UID, 3_600_000, 90, 3_001_000);
@@ -441,14 +443,15 @@
assertThat(item.eventTag.string).startsWith(uid + " ");
assertThat(item.batteryChargeUah).isEqualTo(3_600_000);
assertThat(item.batteryLevel).isEqualTo(90);
- assertThat(item.time).isEqualTo((long) 1_000_000);
+ assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + 1_000_000);
}
assertThat(expectedUid).isEqualTo(200);
}
- private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
- String tag, int uid, int batteryChargeUah, int batteryLevel, long elapsedTimeMs) {
+ private void assertHistoryItem(MockBatteryStatsImpl batteryStats, 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) {
@@ -460,7 +463,7 @@
assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
assertThat(item.batteryLevel).isEqualTo(batteryLevel);
- assertThat(item.time).isEqualTo(elapsedTimeMs);
+ assertThat(item.time).isEqualTo(batteryStats.getMonotonicStartTime() + elapsedTimeMs);
}
@Test
@@ -566,38 +569,66 @@
assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
assertThat(stats.getStatsEndTimestamp()).isEqualTo(55 * 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);
+ assertBatteryConsumer(stats, 180.0, (10 + 20) * MINUTE_IN_MS);
+ assertBatteryConsumer(stats, APP_UID, 180.0, (10 + 20) * MINUTE_IN_MS);
stats.close();
}
@Test
public void accumulateBatteryUsageStats() throws Throwable {
- accumulateBatteryUsageStats(10000000, 1);
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ accumulateBatteryUsageStats(batteryStats, 10000000, 0);
// Accumulate every 200 bytes of battery history
- accumulateBatteryUsageStats(200, 2);
- accumulateBatteryUsageStats(50, 5);
+ accumulateBatteryUsageStats(batteryStats, 200, 2);
+ accumulateBatteryUsageStats(batteryStats, 50, 4);
// Accumulate on every invocation of accumulateBatteryUsageStats
- accumulateBatteryUsageStats(0, 7);
+ accumulateBatteryUsageStats(batteryStats, 0, 7);
}
- private void accumulateBatteryUsageStats(int accumulatedBatteryUsageStatsSpanSize,
- int expectedNumberOfUpdates) throws Throwable {
- BatteryStatsImpl batteryStats = spy(mStatsRule.getBatteryStats());
+ @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);
// Note - these two are in microseconds
when(batteryStats.computeBatteryTimeRemaining(anyLong())).thenReturn(111_000L);
when(batteryStats.computeChargeTimeRemaining(anyLong())).thenReturn(777_000L);
@@ -610,82 +641,76 @@
batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);
}
- PowerStatsStore powerStatsStore = spy(new PowerStatsStore(
- new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()),
- mStatsRule.getHandler()));
- powerStatsStore.reset();
+ mPowerStatsStore.reset();
int[] count = new int[1];
doAnswer(inv -> {
count[0]++;
- return null;
- }).when(powerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
+ return inv.callRealMethod();
+ }).when(mPowerStatsStore).storePowerStatsSpan(any(PowerStatsSpan.class));
- 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 batteryUsageStatsProvider = createBatteryUsageStatsProvider(
+ accumulatedBatteryUsageStatsSpanSize);
- 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);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(20 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOffLocked(APP_UID,
20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(30 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOnLocked(APP_UID,
30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ // 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);
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
// 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);
}
- provider.accumulateBatteryUsageStatsAsync(batteryStats, mStatsRule.getHandler());
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
+ setTime(110 * MINUTE_IN_MS);
synchronized (batteryStats) {
batteryStats.noteFlashlightOffLocked(APP_UID,
110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
}
setTime(115 * MINUTE_IN_MS);
- // Pick up the remainder of battery history that has not yet been accumulated
- provider.accumulateBatteryUsageStats(batteryStats);
+ batteryUsageStatsProvider.accumulateBatteryUsageStatsAsync(batteryStats, handler);
mStatsRule.waitForBackgroundThread();
- BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats,
+ BatteryUsageStats stats = batteryUsageStatsProvider.getBatteryUsageStats(batteryStats,
new BatteryUsageStatsQuery.Builder().accumulated().build());
assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS);
@@ -696,29 +721,55 @@
assertThat(stats.getBatteryCapacity()).isEqualTo(4000); // from PowerProfile
// Total: 10 + 20 + 30 = 60
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ 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
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.0001)
- .of(360.0); // 360 mA * 1.0 hour
- assertThat(stats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ .of(expectedPowerMah);
+ assertThat(aggregatedConsumer
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isEqualTo(60 * MINUTE_IN_MS);
+ .isEqualTo(expectedDurationMs);
+ }
+ private static void assertBatteryConsumer(BatteryUsageStats stats, int uid,
+ double expectedPowerMah, long expectedDurationMs) {
final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream()
- .filter(uid -> uid.getUid() == APP_UID).findFirst().get();
+ .filter(u -> u.getUid() == uid).findFirst().get();
assertThat(uidBatteryConsumer
.getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
.isWithin(0.1)
- .of(360.0);
+ .of(expectedPowerMah);
assertThat(uidBatteryConsumer
.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT))
- .isEqualTo(60 * MINUTE_IN_MS);
-
- assertThat(count[0]).isEqualTo(expectedNumberOfUpdates);
-
- stats.close();
+ .isEqualTo(expectedDurationMs);
}
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 a3c7ece..9e7e0b6 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,6 +41,7 @@
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;
@@ -65,6 +66,7 @@
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;
@@ -118,7 +120,7 @@
clearDirectory();
}
mBatteryStats = new MockBatteryStatsImpl(mBatteryStatsConfigBuilder.build(),
- mMockClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
+ mMockClock, mMonotonicClock, mHistoryDir, mHandler, new PowerStatsUidResolver());
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
synchronized (mBatteryStats) {
@@ -144,6 +146,10 @@
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 b374a32..9a38209 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,9 +77,15 @@
new PowerStatsUidResolver());
}
- MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory,
- Handler handler, PowerStatsUidResolver powerStatsUidResolver) {
- super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler,
+ 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,
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 38fe613..d243f92 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 @@
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
new String[]{"cu570m"},
/* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
- exportAggregatedPowerStats(builder, 3700, 6700);
+ exportAggregatedPowerStats(builder, 3700, 7500);
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 b30710d..d58e0d8 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -19,18 +19,10 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
<uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
<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/AndroidTest.xml b/services/tests/security/intrusiondetection/AndroidTest.xml
index 6489dea4a..0d21158 100644
--- a/services/tests/security/intrusiondetection/AndroidTest.xml
+++ b/services/tests/security/intrusiondetection/AndroidTest.xml
@@ -24,6 +24,10 @@
<option name="install-arg" value="-t" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop intrusiondetection_service_name com.android.coretests.apps.testapp/.TestLoggingService" />
+ </target_preparer>
+
<option name="test-tag" value="IntrusionDetectionServiceTests" />
<test class="com.android.tradefed.testtype.InstrumentationTest" >
<option name="package" value="com.android.server.security.intrusiondetection.tests" />
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 f8cd8f0..0000000
--- 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 e505ebe..5cba6b2 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,12 +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 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,7 +45,6 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
-import android.os.Bundle;
import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.RemoteException;
@@ -61,17 +60,13 @@
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;
@@ -129,28 +124,6 @@
"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 {
@@ -159,6 +132,7 @@
mPermissionEnforcer = new FakePermissionEnforcer();
mPermissionEnforcer.grant(READ_INTRUSION_DETECTION_STATE);
mPermissionEnforcer.grant(MANAGE_INTRUSION_DETECTION_STATE);
+ mPermissionEnforcer.grant(BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE);
mTestLooper = new TestLooper();
mLooper = mTestLooper.getLooper();
@@ -178,6 +152,7 @@
}
@Test
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void testRemoveStateCallback_NoPermission() {
mPermissionEnforcer.revoke(READ_INTRUSION_DETECTION_STATE);
StateCallback scb = new StateCallback();
@@ -229,6 +204,7 @@
}
@Test
+ @Ignore("Unit test does not run as system service UID")
public void testRemoveStateCallback() throws RemoteException {
mIntrusionDetectionService.setState(STATE_DISABLED);
StateCallback scb1 = new StateCallback();
@@ -239,7 +215,6 @@
assertEquals(STATE_DISABLED, scb1.mState);
assertEquals(STATE_DISABLED, scb2.mState);
- doReturn(true).when(mDataAggregator).initialize();
doReturn(true).when(mIntrusionDetectionEventTransportConnection).initialize();
mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);
@@ -252,6 +227,7 @@
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);
@@ -412,39 +388,13 @@
}
@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.
@@ -488,12 +438,12 @@
@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.
@@ -578,6 +528,9 @@
}
@Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
public void test_StartIntrusionDetectionEventTransportService() {
final String TAG = "test_StartIntrusionDetectionEventTransportService";
ServiceConnection serviceConnection = null;
@@ -639,6 +592,20 @@
return serviceConnection;
}
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(
+ android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE)
+ public void testIntrusionDetectionEventTransportConnection_isValidAndBinds()
+ throws InterruptedException {
+ IntrusionDetectionEventTransportConnection intrusionDetectionEventTransportConnection =
+ new IntrusionDetectionEventTransportConnection(mContext);
+ // In a real scenario, the connection will be initialized by the service.
+ // Just to show that the connection is valid and able to bind,
+ // we initialize it here.
+ assertTrue(intrusionDetectionEventTransportConnection.initialize());
+ }
+
private class MockInjector implements IntrusionDetectionService.Injector {
private final Context mContext;
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
index 7cc75ab..a1a7e29 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/TestApp/src/com/android/coretests/apps/testapp/AndroidManifest.xml
@@ -16,9 +16,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.coretests.apps.testapp">
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
<application>
<service android:name=".TestLoggingService"
- android:exported="true" />
+ android:exported="true"
+ android:permission="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE" />
</application>
</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 9850cb0..8024915 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -16,12 +16,7 @@
package com.android.server;
-import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP;
-import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
-
-import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
-import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
-import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
+import static com.android.server.GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -29,27 +24,19 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.PendingIntent;
import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Looper;
import android.os.UserHandle;
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.provider.Settings;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
import android.test.mock.MockContentResolver;
import android.testing.TestableLooper;
@@ -68,7 +55,6 @@
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,8 +62,6 @@
import org.mockito.MockitoAnnotations;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
/**
* Unit tests for {@link GestureLauncherService}.
@@ -106,32 +90,9 @@
private @Mock TelecomManager mTelecomManager;
private @Mock MetricsLogger mMetricsLogger;
@Mock private UiEventLogger mUiEventLogger;
- @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
private MockContentResolver mContentResolver;
private GestureLauncherService mGestureLauncherService;
- private Context mInstrumentationContext =
- InstrumentationRegistry.getInstrumentation().getContext();
-
- private static final String LAUNCH_TEST_WALLET_ACTION = "LAUNCH_TEST_WALLET_ACTION";
- private static final String LAUNCH_FALLBACK_ACTION = "LAUNCH_FALLBACK_ACTION";
- private PendingIntent mGesturePendingIntent =
- PendingIntent.getBroadcast(
- mInstrumentationContext,
- 0,
- new Intent(LAUNCH_TEST_WALLET_ACTION),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
-
- private PendingIntent mFallbackPendingIntent =
- PendingIntent.getBroadcast(
- mInstrumentationContext,
- 0,
- new Intent(LAUNCH_FALLBACK_ACTION),
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
@BeforeClass
public static void oneTimeInitialization() {
if (Looper.myLooper() == null) {
@@ -154,49 +115,9 @@
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getSystemService(Context.TELECOM_SERVICE)).thenReturn(mTelecomManager);
when(mTelecomManager.createLaunchEmergencyDialerIntent(null)).thenReturn(new Intent());
- when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
- mGestureLauncherService =
- new GestureLauncherService(
- mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
-
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- }
-
- private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
- IntentFilter filter = new IntentFilter(action);
- WalletLaunchedReceiver receiver = new WalletLaunchedReceiver();
- mInstrumentationContext.registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
- return receiver;
- }
-
- /**
- * A simple {@link BroadcastReceiver} implementation that counts down a {@link CountDownLatch}
- * when a matching message is received
- */
- private static final class WalletLaunchedReceiver extends BroadcastReceiver {
- private static final int TIMEOUT_SECONDS = 3;
-
- private final CountDownLatch mLatch;
-
- WalletLaunchedReceiver() {
- mLatch = new CountDownLatch(1);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- mLatch.countDown();
- context.unregisterReceiver(this);
- }
-
- Boolean waitUntilShown() {
- try {
- return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
+ mGestureLauncherService = new GestureLauncherService(mContext, mMetricsLogger,
+ mUiEventLogger);
}
@Test
@@ -213,123 +134,37 @@
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(0);
- assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(1);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- }
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertTrue(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertTrue(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
-
- assertFalse(
- mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
-
- @Test
public void testIsEmergencyGestureSettingEnabled_settingDisabled() {
withEmergencyGestureEnabledConfigValue(true);
withEmergencyGestureEnabledSettingValue(false);
@@ -410,9 +245,12 @@
@Test
public void testInterceptPowerKeyDown_firstPowerDownCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- long eventTime = INITIAL_EVENT_TIME_MILLIS + POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ long eventTime = INITIAL_EVENT_TIME_MILLIS +
+ CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
boolean interactive = true;
@@ -446,12 +284,8 @@
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -464,7 +298,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -495,12 +329,8 @@
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -513,7 +343,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -592,7 +422,10 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupComplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -604,7 +437,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -637,145 +470,15 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void
- testInterceptPowerKeyDown_fiveInboundPresses_walletAndEmergencyEnabled_bothLaunch() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableEmergencyGesture();
- enableWalletGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- assertTrue(receiver.waitUntilShown());
-
- // Presses 3 and 4 should not trigger any gesture
- for (int i = 0; i < 2; i++) {
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
- }
-
- // Fifth button press should trigger the emergency flow
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
- verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_intervalInBoundsWalletPowerGesture() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
- assertTrue(receiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletGestureOn_quickAccessWalletServiceUnavailable() {
- when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(false);
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
-
- assertFalse(receiver.waitUntilShown());
- }
-
- @Test
- public void testInterceptPowerKeyDown_walletGestureOn_userSetupIncomplete() {
- WalletLaunchedReceiver receiver = registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(mGesturePendingIntent);
- enableWalletGesture();
- withUserSetupCompleteValue(false);
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- assertFalse(receiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletPowerGesture_nullPendingIntent() {
- WalletLaunchedReceiver gestureReceiver =
- registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(null);
- WalletLaunchedReceiver fallbackReceiver =
- registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
- setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- assertFalse(gestureReceiver.waitUntilShown());
- assertTrue(fallbackReceiver.waitUntilShown());
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
- public void testInterceptPowerKeyDown_walletPowerGesture_intervalOutOfBounds() {
- WalletLaunchedReceiver gestureReceiver =
- registerWalletLaunchedReceiver(LAUNCH_TEST_WALLET_ACTION);
- setUpGetGestureTargetActivityPendingIntent(null);
- WalletLaunchedReceiver fallbackReceiver =
- registerWalletLaunchedReceiver(LAUNCH_FALLBACK_ACTION);
- setUpWalletFallbackPendingIntent(mFallbackPendingIntent);
- enableWalletGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- assertFalse(gestureReceiver.waitUntilShown());
- assertFalse(fallbackReceiver.waitUntilShown());
- }
-
- @Test
public void
testInterceptPowerKeyDown_fiveInboundPresses_cameraAndEmergencyEnabled_bothLaunch() {
- enableCameraGesture();
- enableEmergencyGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ withEmergencyGestureEnabledConfigValue(true);
+ withEmergencyGestureEnabledSettingValue(true);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ mGestureLauncherService.updateEmergencyGestureEnabled();
+ withUserSetupCompleteValue(true);
// First button press does nothing
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -788,7 +491,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 2nd button triggers camera
eventTime += interval;
@@ -877,7 +580,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture (camera gesture disabled)
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -931,7 +634,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
// 3 more button presses which should not trigger any gesture, but intercepts action.
for (int i = 0; i < 3; i++) {
eventTime += interval;
@@ -1034,7 +737,7 @@
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -1062,7 +765,7 @@
interactive, outLaunched);
assertTrue(intercepted);
assertFalse(outLaunched.value);
- interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
}
}
@@ -1213,7 +916,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
@@ -1244,7 +947,9 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnInteractiveSetupIncomplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1257,7 +962,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1290,7 +995,9 @@
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1302,7 +1009,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1335,7 +1042,9 @@
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1378,12 +1087,8 @@
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1396,7 +1101,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1441,7 +1146,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1518,7 +1223,10 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupComplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1530,7 +1238,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1564,7 +1272,9 @@
@Test
public void
testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOnNotInteractiveSetupIncomplete() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
withUserSetupCompleteValue(false);
long eventTime = INITIAL_EVENT_TIME_MILLIS;
@@ -1577,7 +1287,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1622,7 +1332,7 @@
assertFalse(intercepted);
assertFalse(outLaunched.value);
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS;
+ final long interval = CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS;
eventTime += interval;
keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
IGNORED_REPEAT);
@@ -1655,7 +1365,9 @@
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOnNotInteractive() {
- enableCameraGesture();
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+ mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1697,53 +1409,12 @@
}
/**
- * If processPowerKeyDown is called instead of interceptPowerKeyDown (meaning the double tap
- * gesture isn't performed), the emergency gesture is still launched.
- */
- @Test
- public void
- testProcessPowerKeyDown_fiveInboundPresses_cameraDoesNotLaunch_emergencyGestureLaunches() {
- enableCameraGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- //Second event; call processPowerKeyDown without calling interceptPowerKeyDown
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- KeyEvent keyEvent =
- new KeyEvent(
- IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
- mGestureLauncherService.processPowerKeyDown(keyEvent);
-
- verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
- verify(mUiEventLogger, never()).log(any());
-
- // Presses 3 and 4 should not trigger any gesture
- for (int i = 0; i < 2; i++) {
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
- }
-
- // Fifth button press should still trigger the emergency flow
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
- verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
- }
-
- /**
* Helper method to trigger emergency gesture by pressing button for 5 times.
*
* @return last event time.
*/
private long triggerEmergencyGesture() {
- return triggerEmergencyGesture(POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
+ return triggerEmergencyGesture(CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1);
}
/**
@@ -1802,27 +1473,6 @@
.thenReturn(enableConfigValue);
}
- private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
- when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
- .thenReturn(enable);
- }
-
- private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
- enable ? 1 : 0,
- UserHandle.USER_CURRENT);
- }
-
- private void withDefaultDoubleTapPowerGestureAction(int action) {
- Settings.Secure.putIntForUser(
- mContentResolver,
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- action,
- UserHandle.USER_CURRENT);
- }
-
private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
when(mResources.getBoolean(
com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1860,72 +1510,4 @@
userSetupCompleteValue,
UserHandle.USER_CURRENT);
}
-
- private void setUpGetGestureTargetActivityPendingIntent(PendingIntent pendingIntent) {
- doAnswer(
- invocation -> {
- QuickAccessWalletClient.GesturePendingIntentCallback callback =
- (QuickAccessWalletClient.GesturePendingIntentCallback)
- invocation.getArguments()[1];
- callback.onGesturePendingIntentRetrieved(pendingIntent);
- return null;
- })
- .when(mQuickAccessWalletClient)
- .getGestureTargetActivityPendingIntent(any(), any());
- }
-
- private void setUpWalletFallbackPendingIntent(PendingIntent pendingIntent) {
- doAnswer(
- invocation -> {
- QuickAccessWalletClient.WalletPendingIntentCallback callback =
- (QuickAccessWalletClient.WalletPendingIntentCallback)
- invocation.getArguments()[1];
- callback.onWalletPendingIntentRetrieved(pendingIntent);
- return null;
- })
- .when(mQuickAccessWalletClient)
- .getWalletPendingIntent(any(), any());
- }
-
- private void enableWalletGesture() {
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDoubleTapPowerEnabledConfigValue(true);
-
- mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void enableEmergencyGesture() {
- withEmergencyGestureEnabledConfigValue(true);
- withEmergencyGestureEnabledSettingValue(true);
- mGestureLauncherService.updateEmergencyGestureEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void enableCameraGesture() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- }
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
- }
-
- private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
- long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
- KeyEvent keyEvent =
- new KeyEvent(
- IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
- boolean interactive = true;
- MutableBoolean outLaunched = new MutableBoolean(true);
- boolean intercepted =
- mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched);
- assertEquals(intercepted, expectedIntercept);
- assertEquals(outLaunched.value, expectedOutLaunchedValue);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index a8544f6..5862ac6 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -26,6 +26,7 @@
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.content.pm.UserInfo
import android.os.Handler
import android.os.PersistableBundle
@@ -59,6 +60,7 @@
@get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+ @Mock private lateinit var mockPackageManager: PackageManager
@Mock private lateinit var mockUserManagerInternal: UserManagerInternal
private lateinit var context: Context
@@ -68,7 +70,7 @@
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
- context = SupervisionContextWrapper(context)
+ context = SupervisionContextWrapper(context, mockPackageManager)
LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -137,6 +139,31 @@
}
@Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionEnabled_returnsTrue() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, true)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isTrue()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_supervisionUid_supervisionNotEnabled_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID))
+ .thenReturn(arrayOf(systemSupervisionPackage))
+ service.setSupervisionEnabledForUser(USER_ID, false)
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
+ fun isActiveSupervisionApp_notSupervisionUid_returnsFalse() {
+ whenever(mockPackageManager.getPackagesForUid(APP_UID)).thenReturn(arrayOf())
+
+ assertThat(service.mInternal.isActiveSupervisionApp(APP_UID)).isFalse()
+ }
+
+ @Test
fun setSupervisionEnabledForUser() {
assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse()
@@ -191,6 +218,7 @@
private companion object {
const val USER_ID = 100
+ val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
}
}
@@ -198,9 +226,12 @@
* A context wrapper that allows broadcast intents to immediately invoke the receivers without
* performing checks on the sending user.
*/
-private class SupervisionContextWrapper(val context: Context) : ContextWrapper(context) {
+private class SupervisionContextWrapper(val context: Context, val pkgManager: PackageManager) :
+ ContextWrapper(context) {
val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
+ override fun getPackageManager() = pkgManager
+
override fun registerReceiverForAllUsers(
receiver: BroadcastReceiver?,
filter: IntentFilter,
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 0404b82..c4b8599 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 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 @@
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 @@
@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 @@
@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 @@
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 @@
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 90bf1d3..074cbb5 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.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.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 @@
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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 @@
.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 1e9038e..32ba8f5 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 @@
}
@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/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index be516e9..05a1482 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -18,22 +18,15 @@
import static android.view.KeyEvent.KEYCODE_POWER;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
-import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
-import static com.android.server.policy.PhoneWindowManager.POWER_MULTI_PRESS_TIMEOUT_MILLIS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_TO_SLEEP;
-import static org.junit.Assert.assertEquals;
-
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
/**
@@ -46,12 +39,8 @@
@Before
public void setUp() {
setUpPhoneWindowManager();
- mPhoneWindowManager.overrideStatusBarManagerInternal();
}
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
/**
* Power single press to turn screen on/off.
*/
@@ -61,8 +50,6 @@
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertPowerSleep();
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
// turn screen on when begin from non-interactive.
mPhoneWindowManager.overrideDisplayState(Display.STATE_OFF);
sendKey(KEYCODE_POWER);
@@ -103,7 +90,7 @@
mPhoneWindowManager.overrideCanStartDreaming(false);
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertDoublePowerLaunch();
+ mPhoneWindowManager.assertCameraLaunch();
mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
}
@@ -114,7 +101,7 @@
public void testPowerDoublePress() {
sendKey(KEYCODE_POWER);
sendKey(KEYCODE_POWER);
- mPhoneWindowManager.assertDoublePowerLaunch();
+ mPhoneWindowManager.assertCameraLaunch();
}
/**
@@ -127,8 +114,6 @@
sendKey(KEYCODE_POWER, true);
mPhoneWindowManager.assertSearchManagerLaunchAssist();
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
// Show global actions.
mPhoneWindowManager.overrideLongPressOnPower(LONG_PRESS_POWER_GLOBAL_ACTIONS);
sendKey(KEYCODE_POWER, true);
@@ -156,139 +141,4 @@
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertNoPowerSleep();
}
-
- /**
- * Double press of power when the window handles the power key events. The
- * system double power gesture launch should not be performed.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- }
-
- /**
- * Double press of power when the window doesn't handle the power key events.
- * The system default gesture launch should be performed and the app should receive both events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysUnHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Triple press of power when the window handles the power key double press gesture.
- * The system default gesture launch should not be performed, and the app only receives the
- * first two presses.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Tests a single press, followed by a double press when the window can handle the power key.
- * The app should receive all 3 events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePressWithDelay_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 3);
- assertEquals(getUpKeysDispatched(), 3);
- }
-
- /**
- * Tests single press when window doesn't handle the power key. Phone should go to sleep.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyUnhandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
- /**
- * Tests single press when the window handles the power key. Phone should go to sleep after a
- * delay of {POWER_MULTI_PRESS_TIMEOUT_MILLIS}
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
-
- /**
- * Tests 5x press when the window handles the power key. Emergency gesture should still be
- * launched.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerFiveTimesPress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- for (int i = 0; i < 5; ++i) {
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(100);
- }
-
- mPhoneWindowManager.assertEmergencyLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 5919501..9e47a00 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -85,11 +85,7 @@
private Resources mResources;
private PackageManager mPackageManager;
TestPhoneWindowManager mPhoneWindowManager;
-
- DispatchedKeyHandler mDispatchedKeyHandler;
- private int mDownKeysDispatched;
- private int mUpKeysDispatched;
-
+ DispatchedKeyHandler mDispatchedKeyHandler = event -> false;
Context mContext;
/** Modifier key to meta state */
@@ -120,9 +116,6 @@
XmlResourceParser testBookmarks = mResources.getXml(
com.android.frameworks.wmtests.R.xml.bookmarks);
doReturn(testBookmarks).when(mResources).getXml(com.android.internal.R.xml.bookmarks);
- mDispatchedKeyHandler = event -> false;
- mDownKeysDispatched = 0;
- mUpKeysDispatched = 0;
try {
// Keep packageName / className in sync with
@@ -285,14 +278,6 @@
doReturn(expectedBehavior).when(mResources).getInteger(eq(resId));
}
- int getDownKeysDispatched() {
- return mDownKeysDispatched;
- }
-
- int getUpKeysDispatched() {
- return mUpKeysDispatched;
- }
-
private void interceptKey(KeyEvent keyEvent) {
int actions = mPhoneWindowManager.interceptKeyBeforeQueueing(keyEvent);
if ((actions & ACTION_PASS_TO_USER) != 0) {
@@ -300,11 +285,6 @@
if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) {
mPhoneWindowManager.interceptUnhandledKey(keyEvent);
}
- if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
- ++mDownKeysDispatched;
- } else {
- ++mUpKeysDispatched;
- }
}
}
mPhoneWindowManager.dispatchAllPendingEvents();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index f06b45e..9db76d4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -22,7 +22,6 @@
import static android.view.Display.STATE_ON;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
@@ -46,14 +45,10 @@
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
@@ -90,9 +85,7 @@
import android.os.test.TestLooper;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
-import android.service.quickaccesswallet.QuickAccessWalletClient;
import android.telecom.TelecomManager;
-import android.util.MutableBoolean;
import android.view.Display;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
@@ -102,12 +95,9 @@
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
-import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -130,7 +120,6 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
-import org.mockito.stubbing.Answer;
import java.util.List;
import java.util.function.Supplier;
@@ -143,8 +132,6 @@
private PhoneWindowManager mPhoneWindowManager;
private Context mContext;
- private GestureLauncherService mGestureLauncherService;
-
@Mock private WindowManagerInternal mWindowManagerInternal;
@Mock private ActivityManagerInternal mActivityManagerInternal;
@@ -176,9 +163,7 @@
@Mock private DisplayRotation mDisplayRotation;
@Mock private DisplayPolicy mDisplayPolicy;
@Mock private WindowManagerPolicy.ScreenOnListener mScreenOnListener;
- @Mock private QuickAccessWalletClient mQuickAccessWalletClient;
- @Mock private MetricsLogger mMetricsLogger;
- @Mock private UiEventLogger mUiEventLogger;
+ @Mock private GestureLauncherService mGestureLauncherService;
@Mock private GlobalActions mGlobalActions;
@Mock private AccessibilityShortcutController mAccessibilityShortcutController;
@@ -207,8 +192,6 @@
private int mKeyEventPolicyFlags = FLAG_INTERACTIVE;
- private int mProcessPowerKeyDownCount = 0;
-
private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
@@ -277,8 +260,6 @@
MockitoAnnotations.initMocks(this);
mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mGestureLauncherService = spy(new GestureLauncherService(mContext, mMetricsLogger,
- mQuickAccessWalletClient, mUiEventLogger));
setUp(supportSettingsUpdate);
mTestLooper.dispatchAll();
}
@@ -291,7 +272,6 @@
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
.mockStatic(KeyCharacterMap.class)
- .mockStatic(GestureLauncherService.class)
.strictness(Strictness.LENIENT)
.startMocking();
@@ -314,16 +294,6 @@
() -> LocalServices.getService(eq(PowerManagerInternal.class)));
doReturn(mDisplayManagerInternal).when(
() -> LocalServices.getService(eq(DisplayManagerInternal.class)));
- doReturn(true).when(
- () -> GestureLauncherService.isCameraDoubleTapPowerSettingEnabled(any(), anyInt())
- );
- doReturn(true).when(
- () -> GestureLauncherService.isEmergencyGestureSettingEnabled(any(), anyInt())
- );
- doReturn(true).when(
- () -> GestureLauncherService.isGestureLauncherEnabled(any())
- );
- mGestureLauncherService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
doReturn(mGestureLauncherService).when(
() -> LocalServices.getService(eq(GestureLauncherService.class)));
doReturn(mUserManagerInternal).when(
@@ -405,8 +375,7 @@
doNothing().when(mContext).startActivityAsUser(any(), any());
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
- KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0,
- /* inputFeatureFlags = */ 0);
+ KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0);
doReturn(interceptionInfo)
.when(mWindowManagerInternal).getKeyInterceptionInfoFromToken(any());
@@ -424,8 +393,6 @@
eq(TEST_BROWSER_ROLE_PACKAGE_NAME));
doReturn(mSmsIntent).when(mPackageManager).getLaunchIntentForPackage(
eq(TEST_SMS_ROLE_PACKAGE_NAME));
- mProcessPowerKeyDownCount = 0;
- captureProcessPowerKeyDownCount();
Mockito.reset(mContext);
}
@@ -671,12 +638,6 @@
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
- void overrideCanWindowOverridePowerKey(boolean granted) {
- doReturn(granted)
- .when(mButtonOverridePermissionChecker).canWindowOverridePowerKey(any(), anyInt(),
- anyInt());
- }
-
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
@@ -752,59 +713,13 @@
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
}
- void assertDoublePowerLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
+ void assertCameraLaunch() {
mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(2))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
- verify(mGestureLauncherService, atMost(4))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertTrue(mProcessPowerKeyDownCount >= 2 && mProcessPowerKeyDownCount <= 4);
- }
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.contains(true));
+ // GestureLauncherService should receive interceptPowerKeyDown twice.
+ verify(mGestureLauncherService, times(2))
+ .interceptPowerKeyDown(any(), anyBoolean(), any());
}
- void assertNoDoublePowerLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
- mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(0))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.stream().noneMatch(value -> value));
- }
-
- void assertEmergencyLaunch() {
- ArgumentCaptor<MutableBoolean> valueCaptor = ArgumentCaptor.forClass(MutableBoolean.class);
-
- mTestLooper.dispatchAll();
- verify(mGestureLauncherService, atLeast(1))
- .interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
-
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertEquals(mProcessPowerKeyDownCount, 5);
- }
-
- List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
- .map(mutableBoolean -> mutableBoolean.value)
- .toList();
-
- assertTrue(capturedValues.getLast());
- }
-
-
void assertSearchManagerLaunchAssist() {
mTestLooper.dispatchAll();
verify(mSearchManager).launchAssist(any());
@@ -1014,12 +929,4 @@
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
}
-
- private void captureProcessPowerKeyDownCount() {
- doAnswer((Answer<Void>) invocation -> {
- invocation.callRealMethod();
- mProcessPowerKeyDownCount++;
- return null;
- }).when(mGestureLauncherService).processPowerKeyDown(any());
- }
}
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 aa99250..25b9f4b 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 @@
}
@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 @@
@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 @@
}
@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/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 010a322..e7c9e92 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.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 @@
// 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 @@
// initialize mDpmInternal
getDpmInternal();
// initialize mShortcutServiceInternal
+ if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+ getSupervisionManagerInternal();
+ }
getShortcutServiceInternal();
mResponseStatsTracker.onSystemServicesReady(getContext());
@@ -604,6 +609,15 @@
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 @@
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 @@
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 @@
@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/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d89c9c1..7082f00 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2049,11 +2049,15 @@
/**
* 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 @@
* 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 eda0f5b..c4a3670 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl
@@ -24,5 +24,5 @@
* 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 8fe107c..09b18b6 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -109,6 +109,7 @@
//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 59cb5ff..e5f1841 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9756,9 +9756,8 @@
* }</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 +9884,14 @@
"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 +10060,9 @@
/**
* 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 +10181,8 @@
* 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 +11454,7 @@
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/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index 90d6f89..8b52f07 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -66,6 +66,9 @@
// 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 @@
// 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 @@
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 @@
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 127bbff..f8c3287 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -233,6 +233,12 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
|| 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 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/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index e01b10e..bb4ce6e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -16,12 +16,15 @@
package android.telephony;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.OverrideNetworkType;
+import com.android.internal.telephony.flags.Flags;
+
import java.util.Objects;
/**
@@ -94,6 +97,12 @@
private final boolean mIsRoaming;
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsNtn;
+
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ private final boolean mIsSatelliteConstrainedData;
+
/**
* Constructor
*
@@ -106,7 +115,7 @@
@Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType) {
- this(networkType, overrideNetworkType, false);
+ this(networkType, overrideNetworkType, false, false, false);
}
/**
@@ -118,12 +127,37 @@
*
* @hide
*/
+ @Deprecated
public TelephonyDisplayInfo(@NetworkType int networkType,
@OverrideNetworkType int overrideNetworkType,
boolean isRoaming) {
mNetworkType = networkType;
mOverrideNetworkType = overrideNetworkType;
mIsRoaming = isRoaming;
+ mIsNtn = false;
+ mIsSatelliteConstrainedData = false;
+ }
+
+ /**
+ * Constructor
+ *
+ * @param networkType Current packet-switching cellular network type
+ * @param overrideNetworkType The override network type
+ * @param isRoaming True if the device is roaming after override.
+ * @param isNtn True if the device is camped to non-terrestrial network.
+ * @param isSatelliteConstrainedData True if the device satellite internet is bandwidth
+ * constrained.
+ *
+ * @hide
+ */
+ public TelephonyDisplayInfo(@NetworkType int networkType,
+ @OverrideNetworkType int overrideNetworkType,
+ boolean isRoaming, boolean isNtn, boolean isSatelliteConstrainedData) {
+ mNetworkType = networkType;
+ mOverrideNetworkType = overrideNetworkType;
+ mIsRoaming = isRoaming;
+ mIsNtn = isNtn;
+ mIsSatelliteConstrainedData = isSatelliteConstrainedData;
}
/** @hide */
@@ -131,6 +165,8 @@
mNetworkType = p.readInt();
mOverrideNetworkType = p.readInt();
mIsRoaming = p.readBoolean();
+ mIsNtn = p.readBoolean();
+ mIsSatelliteConstrainedData = p.readBoolean();
}
/**
@@ -170,11 +206,34 @@
return mIsRoaming;
}
+ /**
+ * Get whether the satellite internet is with bandwidth constrained capability set.
+ *
+ * @return {@code true} if satellite internet is connected with bandwidth constrained
+ * capability else {@code false}.
+ * @hide
+ */
+ public boolean isSatelliteConstrainedData() {
+ return mIsSatelliteConstrainedData;
+ }
+
+ /**
+ * Get whether the network is a non-terrestrial network.
+ *
+ * @return {@code true} if network is a non-terrestrial network else {@code false}.
+ * @hide
+ */
+ public boolean isNtn() {
+ return mIsNtn;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mNetworkType);
dest.writeInt(mOverrideNetworkType);
dest.writeBoolean(mIsRoaming);
+ dest.writeBoolean(mIsNtn);
+ dest.writeBoolean(mIsSatelliteConstrainedData);
}
public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -202,12 +261,15 @@
TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
return mNetworkType == that.mNetworkType
&& mOverrideNetworkType == that.mOverrideNetworkType
- && mIsRoaming == that.mIsRoaming;
+ && mIsRoaming == that.mIsRoaming
+ && mIsNtn == that.mIsNtn
+ && mIsSatelliteConstrainedData == that.mIsSatelliteConstrainedData;
}
@Override
public int hashCode() {
- return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
+ return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming, mIsNtn,
+ mIsSatelliteConstrainedData);
}
/**
@@ -233,6 +295,8 @@
public String toString() {
return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
+ ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
- + ", isRoaming=" + mIsRoaming + "}";
+ + ", isRoaming=" + mIsRoaming
+ + ", isNtn=" + mIsNtn
+ + ", isSatelliteConstrainedData=" + mIsSatelliteConstrainedData + "}";
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 65a52da..aec11c4 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3114,6 +3114,12 @@
* 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 +3196,7 @@
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
* @see #NETWORK_TYPE_NR
+ * @see #NETWORK_TYPE_NB_IOT_NTN
*
* @hide
*/
@@ -3250,6 +3257,7 @@
* @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 +3408,8 @@
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 +3460,8 @@
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:
@@ -10160,6 +10172,9 @@
* 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 +10224,10 @@
* <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 +10294,7 @@
*/
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);
@@ -14905,7 +14924,8 @@
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 {}
@@ -15006,6 +15026,12 @@
*/
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 +15060,9 @@
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,7 +15074,8 @@
| 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 */
public static final long NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2 = NETWORK_TYPE_BITMASK_CDMA
@@ -18083,7 +18113,7 @@
*/
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/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
index 5e276aa..0a1eedf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -18,6 +18,7 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import com.android.internal.telephony.flags.Flags;
@@ -26,13 +27,14 @@
*
* @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 1025c15..0f23f33 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -560,6 +560,8 @@
* 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 @@
*
* @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 @@
* @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 @@
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
public void requestSelectedNbIotSatelliteSubscriptionId(
@NonNull @CallbackExecutor Executor executor,
@@ -2574,6 +2579,8 @@
*
* @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 @@
* @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 @@
/**
* 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 @@
loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
ex.rethrowAsRuntimeException();
}
- return new ArrayList<>();
+ return new int[0];
}
/**
@@ -2938,8 +2942,9 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
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 d896554..76caef3 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/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 038c6d7..43844f6 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -541,8 +541,7 @@
0
},
"title",
- /* uid = */0,
- /* inputFeatureFlags = */ 0
+ /* uid = */0
)
whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
}
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
new file mode 100644
index 0000000..67d4397a
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_domains.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <certificateTransparency enabled="true" />
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ <trust-anchors>
+ <certificates src="system" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_user.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_user_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <domain>subdomain_inline.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain_inline_ct.android.com</domain>
+ <trust-anchors>
+ <certificates src="@raw/ca_certs_pem" />
+ </trust-anchors>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
new file mode 100644
index 0000000..c35fd71
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/ct_users.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+ <base-config>
+ <trust-anchors>
+ <certificates src="user" />
+ </trust-anchors>
+ </base-config>
+ <domain-config>
+ <domain>android.com</domain>
+ </domain-config>
+ <domain-config>
+ <certificateTransparency enabled="true" />
+ <domain>subdomain.android.com</domain>
+ </domain-config>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 0494f17..c6fe068 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -111,7 +111,8 @@
private NetworkSecurityConfig getSystemStoreConfig() {
return new NetworkSecurityConfig.Builder()
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
}
@@ -141,7 +142,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -159,7 +161,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -178,7 +181,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), true))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), true, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
@@ -245,7 +249,8 @@
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
- new CertificatesEntryRef(SystemCertificateSource.getInstance(), false))
+ new CertificatesEntryRef(
+ SystemCertificateSource.getInstance(), false, false))
.build();
ArraySet<Pair<Domain, NetworkSecurityConfig>> domainMap
= new ArraySet<Pair<Domain, NetworkSecurityConfig>>();
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index 81e05c1..542465d 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -502,4 +502,47 @@
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
TestUtils.assertConnectionSucceeds(context, "developer.android.com", 443);
}
+
+ public void testCertificateTransparencyDomainConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_domains,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_user_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline.android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain_inline_ct.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
+
+ public void testCertificateTransparencyUserConfig() throws Exception {
+ XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.ct_users,
+ TestUtils.makeApplicationInfo());
+ ApplicationConfig appConfig = new ApplicationConfig(source);
+ assertTrue(appConfig.hasPerDomainConfigs());
+ NetworkSecurityConfig config = appConfig.getConfigForHostname("");
+ assertNotNull(config);
+ // Check defaults.
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("android.com");
+ assertFalse(config.isCertificateTransparencyVerificationRequired());
+
+ config = appConfig.getConfigForHostname("subdomain.android.com");
+ assertTrue(config.isCertificateTransparencyVerificationRequired());
+ }
}
diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
index af87bf7..49616c3 100644
--- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java
@@ -83,6 +83,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -112,6 +113,7 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -141,6 +143,7 @@
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -231,31 +234,37 @@
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
}
@@ -272,6 +281,7 @@
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -281,6 +291,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -289,6 +300,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
}
@@ -305,18 +317,21 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -326,24 +341,28 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(3);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(4);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(4);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(5);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(5);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(6);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -352,6 +371,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(6);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(7);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -362,6 +382,7 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -379,12 +400,14 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(1);
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(1);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(2);
@@ -394,6 +417,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
verify(rollbackObserver).onExecuteBootLoopMitigation(2);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -402,6 +426,7 @@
watchdog.noteBoot();
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(2);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(3);
verify(rollbackObserver, never()).onExecuteBootLoopMitigation(3);
@@ -412,6 +437,7 @@
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
verify(rescuePartyObserver).onExecuteBootLoopMitigation(1);
verify(rescuePartyObserver, never()).onExecuteBootLoopMitigation(2);
}
@@ -739,14 +765,14 @@
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
- watchdog.registerHealthObserver(rollbackObserver);
+ watchdog.registerHealthObserver(rollbackObserver, mTestExecutor);
return rollbackObserver;
}
RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) {
setCrashRecoveryPropRescueBootCount(0);
RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext));
assertFalse(RescueParty.isRebootPropertySet());
- watchdog.registerHealthObserver(rescuePartyObserver);
+ watchdog.registerHealthObserver(rescuePartyObserver, mTestExecutor);
return rescuePartyObserver;
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index 5a8a6be..c64dc72 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -53,6 +53,7 @@
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
+import android.util.Slog;
import android.util.Xml;
import androidx.test.InstrumentationRegistry;
@@ -88,6 +89,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -119,6 +121,7 @@
private final TestClock mTestClock = new TestClock();
private TestLooper mTestLooper;
+ private Executor mTestExecutor;
private Context mSpyContext;
// Keep track of all created watchdogs to apply device config changes
private List<PackageWatchdog> mAllocatedWatchdogs;
@@ -155,6 +158,7 @@
Manifest.permission.WRITE_DEVICE_CONFIG,
Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG);
mTestLooper = new TestLooper();
+ mTestExecutor = mTestLooper.getNewExecutor();
mSpyContext = spy(InstrumentationRegistry.getContext());
when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
@@ -226,7 +230,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -242,8 +247,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -260,7 +267,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -276,8 +284,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.unregisterHealthObserver(observer2);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -294,7 +304,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -310,8 +321,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), LONG_DURATION);
moveTimeForwardAndDispatch(SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -330,13 +343,14 @@
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
// Start observing APP_A
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time half-way
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
// Start observing APP_A again
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then advance time such that it should have expired were it not for the second observation
moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
@@ -358,15 +372,17 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer1, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog1.registerHealthObserver(observer2, mTestExecutor);
+ watchdog1.startExplicitHealthCheck(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
// Then advance time and run IO Handler so file is saved
mTestLooper.dispatchAll();
// Then start a new watchdog
PackageWatchdog watchdog2 = createWatchdog();
// Then resume observer1 and observer2
- watchdog2.registerHealthObserver(observer1);
- watchdog2.registerHealthObserver(observer2);
+ watchdog2.registerHealthObserver(observer1, mTestExecutor);
+ watchdog2.registerHealthObserver(observer2, mTestExecutor);
raiseFatalFailureAndDispatch(watchdog2,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
new VersionedPackage(APP_B, VERSION_CODE)),
@@ -374,6 +390,7 @@
// We should receive failed packages as expected to ensure observers are persisted and
// resumed correctly
+ mTestLooper.dispatchAll();
assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
}
@@ -387,8 +404,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A below the threshold
for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
@@ -414,9 +433,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
-
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_B), SHORT_DURATION);
// Then fail APP_C (not observed) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -448,7 +468,8 @@
}
};
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A (different version) above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -477,13 +498,17 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -523,13 +548,17 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
- watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
+ watchdog.registerHealthObserver(observerNone, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
SHORT_DURATION);
- watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
+ watchdog.registerHealthObserver(observerHigh, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
SHORT_DURATION);
- watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
+ watchdog.registerHealthObserver(observerMid, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerMid, Arrays.asList(APP_A, APP_B),
SHORT_DURATION);
- watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
+ watchdog.registerHealthObserver(observerLow, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerLow, Arrays.asList(APP_A),
SHORT_DURATION);
// Then fail all apps above the threshold
@@ -577,8 +606,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -641,8 +672,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
- watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
- watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerFirst, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observerSecond, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -709,8 +742,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -731,8 +766,10 @@
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
// Start observing for observer1 and observer2 with failure handling
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
// Then fail APP_A above the threshold
raiseFatalFailureAndDispatch(watchdog,
@@ -762,8 +799,10 @@
// Start observing with explicit health checks for APP_A and APP_B respectively
// with observer1 and observer2
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -779,7 +818,8 @@
// Observer3 didn't exist when we got the explicit health check above, so
// it starts out with a non-passing explicit health check and has to wait for a pass
// otherwise it would be notified of APP_A failure on expiry
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer3, Arrays.asList(APP_A), SHORT_DURATION);
// Then expire observers
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -809,8 +849,9 @@
// Start observing with explicit health checks for APP_A and APP_B
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_B), LONG_DURATION);
// Run handler so requests are dispatched to the controller
mTestLooper.dispatchAll();
@@ -846,7 +887,7 @@
// Then set new supported packages
controller.setSupportedPackages(Arrays.asList(APP_C));
// Start observing APP_A and APP_C; only APP_C has support for explicit health checks
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
// Run handler so requests/cancellations are dispatched to the controller
mTestLooper.dispatchAll();
@@ -877,7 +918,8 @@
// package observation duration == LONG_DURATION
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), LONG_DURATION);
// Then APP_A has exceeded health check duration
moveTimeForwardAndDispatch(SHORT_DURATION);
@@ -908,7 +950,8 @@
// package observation duration == SHORT_DURATION / 2
// health check duration == SHORT_DURATION (set by default in the TestController)
controller.setSupportedPackages(Arrays.asList(APP_A));
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
// Forward time to expire the observation duration
moveTimeForwardAndDispatch(SHORT_DURATION / 2);
@@ -981,7 +1024,7 @@
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1001,7 +1044,7 @@
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
- wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
+ wd.startExplicitHealthCheck(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
@@ -1022,7 +1065,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), SHORT_DURATION);
// Fail APP_A below the threshold which should not trigger package failures
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1050,7 +1094,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
@@ -1075,15 +1120,16 @@
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_beforeExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
// Note: Don't move too close to the expiration time otherwise the handler will be thrashed
// by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
// small timeouts.
@@ -1097,15 +1143,16 @@
}
/**
- * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
- * an invalid durationMs.
+ * Test default monitoring duration is used when PackageWatchdog#startExplicitHealthCheck is
+ * offered an invalid durationMs.
*/
@Test
public void testInvalidMonitoringDuration_afterExpiry() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), -1);
moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
raiseFatalFailureAndDispatch(watchdog,
Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
@@ -1127,7 +1174,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
// Raise 2 failures at t=0 and t=900 respectively
watchdog.notifyPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1154,8 +1202,10 @@
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
- watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer2, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
@@ -1174,7 +1224,8 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
@@ -1194,7 +1245,8 @@
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(true);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1212,7 +1264,8 @@
persistentObserver.setPersistent(true);
persistentObserver.setMayObservePackages(false);
- watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+ watchdog.registerHealthObserver(persistentObserver, mTestExecutor);
+ watchdog.startExplicitHealthCheck(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
@@ -1223,13 +1276,15 @@
/** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
@Test
public void testBootLoopDetection_meetsThreshold() {
+ Slog.w("hrm1243", "I should definitely be here try 1 ");
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1237,10 +1292,11 @@
public void testBootLoopDetection_meetsThresholdRecoverability() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isTrue();
}
@@ -1252,10 +1308,11 @@
public void testBootLoopDetection_doesNotMeetThreshold() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1268,10 +1325,11 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mitigatedBootLoop()).isFalse();
}
@@ -1286,11 +1344,12 @@
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1302,11 +1361,12 @@
bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver1);
- watchdog.registerHealthObserver(bootObserver2);
+ watchdog.registerHealthObserver(bootObserver1, mTestExecutor);
+ watchdog.registerHealthObserver(bootObserver2, mTestExecutor);
for (int i = 0; i < 15; i++) {
watchdog.noteBoot();
}
+ mTestLooper.dispatchAll();
assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
@@ -1319,7 +1379,7 @@
mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; j++) {
watchdog.noteBoot();
@@ -1333,7 +1393,7 @@
watchdog.noteBoot();
}
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1342,7 +1402,7 @@
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
- watchdog.registerHealthObserver(bootObserver);
+ watchdog.registerHealthObserver(bootObserver, mTestExecutor);
for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) {
watchdog.noteBoot();
}
@@ -1358,7 +1418,7 @@
for (int i = 0; i < 4; i++) {
watchdog.noteBoot();
}
-
+ mTestLooper.dispatchAll();
assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4));
}
@@ -1370,7 +1430,8 @@
public void testNullFailedPackagesList() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(observer1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer1, List.of(APP_A), LONG_DURATION);
raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
assertThat(observer1.mMitigatedPackages).isEmpty();
@@ -1388,18 +1449,18 @@
PackageWatchdog watchdog = createWatchdog(testController, true);
TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
- watchdog.registerHealthObserver(testObserver1);
- watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver1, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver1, List.of(APP_A), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
- watchdog.registerHealthObserver(testObserver2);
- watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver2, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver2, List.of(APP_B), LONG_DURATION);
mTestLooper.dispatchAll();
TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
- watchdog.registerHealthObserver(testObserver3);
- watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
+ watchdog.registerHealthObserver(testObserver3, mTestExecutor);
+ watchdog.startExplicitHealthCheck(testObserver3, List.of(APP_C), LONG_DURATION);
mTestLooper.dispatchAll();
watchdog.unregisterHealthObserver(testObserver1);
@@ -1431,14 +1492,15 @@
public void testFailureHistoryIsPreserved() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), SHORT_DURATION);
for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
mTestLooper.dispatchAll();
assertThat(observer.mMitigatedPackages).isEmpty();
- watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A), LONG_DURATION);
watchdog.notifyPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
PackageWatchdog.FAILURE_REASON_UNKNOWN);
mTestLooper.dispatchAll();
@@ -1453,7 +1515,8 @@
public void testMitigationSlidingWindow() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer = new TestObserver(OBSERVER_NAME_1);
- watchdog.startObservingHealth(observer, List.of(APP_A),
+ watchdog.registerHealthObserver(observer, mTestExecutor);
+ watchdog.startExplicitHealthCheck(observer, List.of(APP_A),
PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS * 2);
@@ -1895,6 +1958,7 @@
}
public boolean onExecuteBootLoopMitigation(int level) {
+ Slog.w("hrm1243", "I'm here " + level);
mMitigatedBootLoop = true;
mBootMitigationCounts.add(level);
return true;